<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
     
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  

</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      
<section class="cover">
    
      
      <a class="forkMe" href="https://github.com/Shen-Yu/hexo-theme-ayer"
        target="_blank"><img width="149" height="149" src="/images/forkme.png"
          class="attachment-full size-full" alt="Fork me on GitHub" data-recalc-dims="1"></a>
    
  <div class="cover-frame">
    <div class="bg-box">
      <img src="/images/cover1.jpg" alt="image frame" />
    </div>
    <div class="cover-inner text-center text-white">
      <h1><a href="/">Hexo</a></h1>
      <div id="subtitle-box">
        
        <span id="subtitle"></span>
        
      </div>
      <div>
        
      </div>
    </div>
  </div>
  <div class="cover-learn-more">
    <a href="javascript:void(0)" class="anchor"><i class="ri-arrow-down-line"></i></a>
  </div>
</section>



<script src="https://cdn.jsdelivr.net/npm/typed.js@2.0.11/lib/typed.min.js"></script>


<!-- Subtitle -->

  <script>
    try {
      var typed = new Typed("#subtitle", {
        strings: ['面朝大海，春暖花开', '何来天才，唯有苦练', '集中一点，登峰造极'],
        startDelay: 0,
        typeSpeed: 200,
        loop: true,
        backSpeed: 100,
        showCursor: true
      });
    } catch (err) {
      console.log(err)
    }
  </script>
  
<div id="main">
  <section class="outer">
  
  

<div class="notice" style="margin-top:50px">
    <i class="ri-heart-fill"></i>
    <div class="notice-content" id="broad"></div>
</div>
<script type="text/javascript">
    fetch('https://v1.hitokoto.cn')
        .then(response => response.json())
        .then(data => {
            document.getElementById("broad").innerHTML = data.hitokoto;
        })
        .catch(console.error)
</script>

<style>
    .notice {
        padding: 20px;
        border: 1px dashed #e6e6e6;
        color: #969696;
        position: relative;
        display: inline-block;
        width: 100%;
        background: #fbfbfb50;
        border-radius: 10px;
    }

    .notice i {
        float: left;
        color: #999;
        font-size: 16px;
        padding-right: 10px;
        vertical-align: middle;
        margin-top: -2px;
    }

    .notice-content {
        display: initial;
        vertical-align: middle;
    }
</style>
  
  <article class="articles">
    
    
    
    
    <article
  id="post-django/12-接入三方平台"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/django/12-%E6%8E%A5%E5%85%A5%E4%B8%89%E6%96%B9%E5%B9%B3%E5%8F%B0/"
    >12-接入三方平台.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/django/12-%E6%8E%A5%E5%85%A5%E4%B8%89%E6%96%B9%E5%B9%B3%E5%8F%B0/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/django/">django</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="接入三方平台"><a href="#接入三方平台" class="headerlink" title="接入三方平台"></a>接入三方平台</h2><p>在Web应用的开发过程中，有一些任务并不是我们自己能够完成的。例如，我们的Web项目中需要做个人或企业的实名认证，很显然我们并没有能力判断用户提供的认证信息的真实性，这个时候我们就要借助三方平台提供的服务来完成该项操作。再比如说，我们的项目中需要提供在线支付功能，这类业务通常也是借助支付网关来完成而不是自己去实现，我们只需要接入像微信、支付宝、银联这样的三方平台即可。</p>
<p>在项目中接入三方平台基本上就两种方式：API接入和SDK接入。</p>
<ol>
<li>API接入指的是通过访问三方提供的URL来完成操作或获取数据。国内有很多这样的平台提供了大量常用的服务，例如<a target="_blank" rel="noopener" href="https://www.juhe.cn/">聚合数据</a>上提供了生活服务类、金融科技类、交通地理类、充值缴费类等各种类型的API。我们可以通过Python程序发起网络请求，通过访问URL获取数据，这些API接口跟我们项目中提供的数据接口是一样的，只不过我们项目中的API是供自己使用的，而这类三方平台提供的API是开放的。当然开放并不代表免费，大多数能够提供有商业价值的数据的API都是需要付费才能使用的。</li>
<li>SDK接入指的是通过安装三方库并使用三方库封装的类、函数来使用三方平台提供的服务的方式。例如我们刚才说到的接入支付宝，就需要先安装支付宝的SDK，然后通过支付宝封装的类和方法完成对支付服务的调用。</li>
</ol>
<p>下面我们通过具体的例子来讲解如何接入三方平台。</p>
<h3 id="接入短信网关"><a href="#接入短信网关" class="headerlink" title="接入短信网关"></a>接入短信网关</h3><p>一个Web项目有很多地方都可以用到短信服务，例如：手机验证码登录、重要消息提醒、产品营销短信等。要实现发送短信的功能，可以通过接入短信网关来实现，国内比较有名的短信网关包括：云片短信、网易云信、螺丝帽、SendCloud等，这些短信网关一般都提供了免费试用功能。下面我们以<a target="_blank" rel="noopener" href="https://luosimao.com/">螺丝帽</a>平台为例，讲解如何在项目中接入短信网关，其他平台操作基本类似。</p>
<ol>
<li><p>注册账号，新用户可以免费试用。</p>
</li>
<li><p>登录到管理后台，进入短信版块。</p>
</li>
<li><p>点击“触发发送”可以找到自己专属的API Key（身份标识）。</p>
<p> <img src="http://iubest.gitee.io/pic/luosimao-sms-apikey.png" alt="img"></p>
</li>
<li><p>点击“签名管理”可以添加短信签名，短信都必须携带签名，免费试用的短信要在短信中添加“【铁壳测试】”这个签名，否则短信无法发送。</p>
<p> <img src="http://iubest.gitee.io/pic/luosimao-sms-signature.png" alt="img"></p>
</li>
<li><p>点击“IP白名单”将运行Django项目的服务器地址（公网IP地址，本地运行可以打开<a href="">xxx</a>网站查看自己本机的公网IP地址）填写到白名单中，否则短信无法发送。</p>
<p> <img src="http://iubest.gitee.io/pic/luosimao-sms-whitelist.png" alt="img"></p>
</li>
<li><p>如果没有剩余的短信条数，可以到“充值”页面选择“短信服务”进行充值。</p>
<p> <img src="http://iubest.gitee.io/pic/luosimao-pay-onlinebuy.png" alt="img"></p>
</li>
</ol>
<p>接下来，我们可以通过调用螺丝帽短信网关实现发送短信验证码的功能，代码如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">send_mobile_code</span>(<span class="params">tel, code</span>):</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;发送短信验证码&quot;&quot;&quot;</span></span><br><span class="line">    resp = requests.post(</span><br><span class="line">        url=<span class="string">&#x27;http://sms-api.luosimao.com/v1/send.json&#x27;</span>,</span><br><span class="line">        auth=(<span class="string">&#x27;api&#x27;</span>, <span class="string">&#x27;key-自己的APIKey&#x27;</span>),</span><br><span class="line">        data=&#123;</span><br><span class="line">            <span class="string">&#x27;mobile&#x27;</span>: tel,</span><br><span class="line">            <span class="string">&#x27;message&#x27;</span>: <span class="string">f&#x27;您的短信验证码是<span class="subst">&#123;code&#125;</span>，打死也不能告诉别人哟。【Python小课】&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        verify=<span class="literal">False</span></span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> resp.json()</span><br></pre></td></tr></table></figure>

<p>运行上面的代码需要先安装<code>requests</code>三方库，这个三方库封装了HTTP网络请求的相关功能，使用起来非常的简单，我们在之前的内容中也讲到过这个三方库。<code>send_mobile_code</code>函数有两个参数，第一个参数是手机号，第二个参数是短信验证码的内容，第5行代码需要提供自己的API Key，就是上面第2步中查看到的自己的API Key。请求螺丝帽的短信网关会返回JSON格式的数据，对于上面的代码如果返回<code>&#123;&#39;err&#39;: 0, &#39;msg&#39;: &#39;ok&#39;&#125;</code>，则表示短信发送成功，如果<code>err</code>字段的值不为<code>0</code>而是其他值，则表示短信发送失败，可以在螺丝帽官方的<a target="_blank" rel="noopener" href="https://luosimao.com/docs/api/">开发文档</a>页面上查看到不同的数值代表的含义，例如：<code>-20</code>表示余额不足，<code>-32</code>表示缺少短信签名。</p>
<p>可以在视图函数中调用上面的函数来完成发送短信验证码的功能，稍后我们可以把这个功能跟用户注册结合起来。</p>
<p>生成随机验证码和验证手机号的函数。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line">TEL_PATTERN = re.compile(<span class="string">r&#x27;1[3-9]\d&#123;9&#125;&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">check_tel</span>(<span class="params">tel</span>):</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;检查手机号&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> TEL_PATTERN.fullmatch(tel) <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">random_code</span>(<span class="params">length=<span class="number">6</span></span>):</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;生成随机短信验证码&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span>.join(random.choices(<span class="string">&#x27;0123456789&#x27;</span>, k=length))</span><br></pre></td></tr></table></figure>

<p>发送短信验证码的视图函数。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@api_view((&#x27;GET&#x27;, ))</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_mobilecode</span>(<span class="params">request, tel</span>):</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;获取短信验证码&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">if</span> check_tel(tel):</span><br><span class="line">        redis_cli = get_redis_connection()</span><br><span class="line">        <span class="keyword">if</span> redis_cli.exists(<span class="string">f&#x27;vote:block-mobile:<span class="subst">&#123;tel&#125;</span>&#x27;</span>):</span><br><span class="line">            data = &#123;<span class="string">&#x27;code&#x27;</span>: <span class="number">30001</span>, <span class="string">&#x27;message&#x27;</span>: <span class="string">&#x27;请不要在60秒内重复发送短信验证码&#x27;</span>&#125;</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            code = random_code()</span><br><span class="line">            send_mobile_code(tel, code)</span><br><span class="line">            <span class="comment"># 通过Redis阻止60秒内容重复发送短信验证码</span></span><br><span class="line">            redis_cli.set(<span class="string">f&#x27;vote:block-mobile:<span class="subst">&#123;tel&#125;</span>&#x27;</span>, <span class="string">&#x27;x&#x27;</span>, ex=<span class="number">60</span>)</span><br><span class="line">            <span class="comment"># 将验证码在Redis中保留10分钟（有效期10分钟）</span></span><br><span class="line">            redis_cli.set(<span class="string">f&#x27;vote2:valid-mobile:<span class="subst">&#123;tel&#125;</span>&#x27;</span>, code, ex=<span class="number">600</span>)</span><br><span class="line">            data = &#123;<span class="string">&#x27;code&#x27;</span>: <span class="number">30000</span>, <span class="string">&#x27;message&#x27;</span>: <span class="string">&#x27;短信验证码已发送，请注意查收&#x27;</span>&#125;</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        data = &#123;<span class="string">&#x27;code&#x27;</span>: <span class="number">30002</span>, <span class="string">&#x27;message&#x27;</span>: <span class="string">&#x27;请输入有效的手机号&#x27;</span>&#125;</span><br><span class="line">    <span class="keyword">return</span> Response(data)</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>说明</strong>：上面的代码利用Redis实现了两个额外的功能，一个是阻止用户60秒内重复发送短信验证码，一个是将用户的短信验证码保留10分钟，也就是说这个短信验证码的有效期只有10分钟，我们可以要求用户在注册时提供该验证码来验证用户手机号的真实性。</p>
</blockquote>
<h3 id="接入云存储服务"><a href="#接入云存储服务" class="headerlink" title="接入云存储服务"></a>接入云存储服务</h3><p>当我们提到<strong>云存储</strong>这个词的时候，通常是指把数据存放在由第三方提供的虚拟服务器环境下，简单的说就是将某些数据或资源通过第三平台托管。一般情况下，提供云存储服务的公司都运营着大型的数据中心，需要云存储服务的个人或组织通过向其购买或租赁存储空间来满足数据存储的需求。在开发Web应用时，可以将静态资源，尤其是用户上传的静态资源直接置于云存储服务中，云存储通常会提供对应的URL使得用户可以访问该静态资源。国内外比较有名的云存储服务（如：亚马逊的S3、阿里的OSS2等）一般都物美价廉，相比自己架设静态资源服务器，云存储的代价更小，而且一般的云存储平台都提供了CDN服务，用于加速对静态资源的访问，所以不管从哪个角度出发，使用云存储的方式管理Web应用的数据和静态资源都是非常好的选择，除非这些资源涉及到个人或商业隐私，否则就可以托管到云存储中。</p>
<p>下面我们以接入<a target="_blank" rel="noopener" href="https://www.qiniu.com/">七牛云</a>为例，讲解如何实现将用户上传的文件保存到七牛云存储。七牛云是国内知名的云计算及数据服务提供商，七牛云在海量文件存储、CDN、视频点播、互动直播以及大规模异构数据的智能分析与处理等领域都有自己的产品，而且非付费用户也可以免费接入，使用其提供的服务。下面是接入七牛云的流程：</p>
<ol>
<li><p>注册账号，登录管理控制台。</p>
<p> <img src="http://iubest.gitee.io/pic/qiniu-manage-console.png" alt="img"></p>
</li>
<li><p>选择左侧菜单中的对象存储。</p>
<p> <img src="http://iubest.gitee.io/pic/qiniu-storage-service.png" alt="img"></p>
</li>
<li><p>在空间管理中选择新建空间（例如：myvote），如果提示空间名称已被占用，更换一个再尝试即可。注意，创建空间后会提示绑定自定义域名，如果暂时还没有自己的域名，可以使用七牛云提供的临时域名，但是临时域名会在30天后被回收，所以最好准备自己的域名（域名需要备案，不清楚如何操作的请自行查阅相关资料）。</p>
<p> <img src="http://iubest.gitee.io/pic/qiniu-storage-create.png" alt="img"></p>
</li>
<li><p>在网页的右上角点击个人头像中的“密钥管理”，查看自己的密钥，稍后在代码中需要使用AK（AccessKey）和SK（SecretKey）两个密钥来认证用户身份。</p>
<p> <img src="http://iubest.gitee.io/pic/qiniu-secretkey-management.png" alt="img"></p>
</li>
<li><p>点击网页上方菜单中的“文档”，进入到<a target="_blank" rel="noopener" href="https://developer.qiniu.com/">七牛开发者中心</a>，选择导航菜单中的“SDK&amp;工具”并点击“官方SDK”子菜单，找到Python（服务端）并点击“文档”查看官方文档。</p>
<p> <img src="http://iubest.gitee.io/pic/qiniu-document-python.png" alt="img"></p>
</li>
</ol>
<p>接下来，只要安装官方文档提供的示例，就可以接入七牛云，使用七牛云提供的云存储以及其他服务。首先可以通过下面的命令安装七牛云的三方库。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install qiniu</span><br></pre></td></tr></table></figure>

<p>接下来可以通过<code>qiniu</code>模块中的<code>put_file</code>和<code>put_stream</code>两个函数实现文件上传，前者可以上传指定路径的文件，后者可以将内存中的二进制数据上传至七牛云，具体的代码如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> qiniu</span><br><span class="line"></span><br><span class="line">AUTH = qiniu.Auth(<span class="string">&#x27;密钥管理中的AccessKey&#x27;</span>, <span class="string">&#x27;密钥管理中的SecretKey&#x27;</span>)</span><br><span class="line">BUCKET_NAME = <span class="string">&#x27;myvote&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">upload_file_to_qiniu</span>(<span class="params">key, file_path</span>):</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;上传指定路径的文件到七牛云&quot;&quot;&quot;</span></span><br><span class="line">    token = AUTH.upload_token(BUCKET_NAME, key)</span><br><span class="line">    <span class="keyword">return</span> qiniu.put_file(token, key, file_path)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">upload_stream_to_qiniu</span>(<span class="params">key, stream, size</span>):</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;上传二进制数据流到七牛云&quot;&quot;&quot;</span></span><br><span class="line">    token = AUTH.upload_token(BUCKET_NAME, key)</span><br><span class="line">    <span class="keyword">return</span> qiniu.put_stream(token, key, stream, <span class="literal">None</span>, size)</span><br></pre></td></tr></table></figure>

<p>下面是一个文件上传的简单前端页。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>上传文件<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">form</span> <span class="attr">action</span>=<span class="string">&quot;/upload/&quot;</span> <span class="attr">method</span>=<span class="string">&quot;post&quot;</span> <span class="attr">enctype</span>=<span class="string">&quot;multipart/form-data&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;file&quot;</span> <span class="attr">name</span>=<span class="string">&quot;photo&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span> <span class="attr">value</span>=<span class="string">&quot;上传&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>说明</strong>：前端如果使用表单实现文件上传，表单的method属性必须设置为post，enctype属性需要设置为multipart/form-data，表单中type属性为file的input标签，就是上传文件的文件选择器。</p>
</blockquote>
<p>实现上传功能的视图函数如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.views.decorators.csrf <span class="keyword">import</span> csrf_exempt</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@csrf_exempt</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">upload</span>(<span class="params">request</span>):</span></span><br><span class="line">    <span class="comment"># 如果上传的文件小于2.5M，则photo对象的类型为InMemoryUploadedFile，文件在内存中</span></span><br><span class="line">    <span class="comment"># 如果上传的文件超过2.5M，则photo对象的类型为TemporaryUploadedFile，文件在临时路径下</span></span><br><span class="line">    photo = request.FILES.get(<span class="string">&#x27;photo&#x27;</span>)</span><br><span class="line">    _, ext = os.path.splitext(photo.name)</span><br><span class="line">    <span class="comment"># 通过UUID和原来文件的扩展名生成独一无二的新的文件名</span></span><br><span class="line">    filename = <span class="string">f&#x27;<span class="subst">&#123;uuid.uuid1().hex&#125;</span><span class="subst">&#123;ext&#125;</span>&#x27;</span></span><br><span class="line">    <span class="comment"># 对于内存中的文件，可以使用上面封装好的函数upload_stream_to_qiniu上传文件到七牛云</span></span><br><span class="line">    <span class="comment"># 如果文件保存在临时路径下，可以使用upload_file_to_qiniu实现文件上传</span></span><br><span class="line">    upload_stream_to_qiniu(filename, photo.file, photo.size)</span><br><span class="line">    <span class="keyword">return</span> redirect(<span class="string">&#x27;/static/html/upload.html&#x27;</span>)</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>注意</strong>：上面的视图函数使用了<code>csrf_exempt</code>装饰器，该装饰器能够让表单免除必须提供CSRF令牌的要求。此外，代码第11行使用了<code>uuid</code>模块的<code>uuid1</code>函数来生成全局唯一标识符。</p>
</blockquote>
<p>运行项目尝试文件上传的功能，文件上传成功后，可以在七牛云“空间管理”中点击自己空间并进入“文件管理”界面，在这里可以看到我们刚才上传成功的文件，而且可以通过七牛云提供的域名获取该文件。</p>
<p><img src="http://iubest.gitee.io/pic/qiniu-file-management.png" alt="img"></p>
<p>�且可以通过七牛云提供的域名获取该文件。</p>
<p><img src="http://iubest.gitee.io/pic/qiniu-file-management.png" alt="img"></p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/django/" rel="tag">django</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-django/3-静态资源和Ajax请求"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/django/3-%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E5%92%8CAjax%E8%AF%B7%E6%B1%82/"
    >3-静态资源和Ajax请求.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/django/3-%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E5%92%8CAjax%E8%AF%B7%E6%B1%82/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/django/">django</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="静态资源和Ajax请求"><a href="#静态资源和Ajax请求" class="headerlink" title="静态资源和Ajax请求"></a>静态资源和Ajax请求</h2><h3 id="加载静态资源"><a href="#加载静态资源" class="headerlink" title="加载静态资源"></a>加载静态资源</h3><p>如果要在Django项目中使用静态资源，可以先创建一个用于保存静态资源的目录。在<code>vote</code>项目中，我们将静态资源置于名为<code>static</code>的文件夹中，在该文件夹包含了三个子文件夹：css、js和images，分别用来保存外部CSS文件、外部JavaScript文件和图片资源，如下图所示。</p>
<p><img src="http://iubest.gitee.io/pic/pycharm-django-static.png" alt="img"></p>
<p>为了能够找到保存静态资源的文件夹，我们还需要修改Django项目的配置文件<code>settings.py</code>，如下所示：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">STATICFILES_DIRS = [os.path.join(BASE_DIR, <span class="string">&#x27;static&#x27;</span>), ]</span><br><span class="line">STATIC_URL = <span class="string">&#x27;/static/&#x27;</span></span><br></pre></td></tr></table></figure>

<p>配置好静态资源之后，大家可以运行项目，然后看看之前我们写的页面上的图片是否能够正常加载出来。需要说明的是，在项目正式部署到线上环境后，我们通常会把静态资源交给专门的静态资源服务器（如Nginx、Apache）来处理，而不是有运行Python代码的服务器来管理静态资源，所以上面的配置并不适用于生产环境，仅供项目开发阶段测试使用。使用静态资源的正确姿势我们会在后续的章节为大家讲解。</p>
<h3 id="Ajax概述"><a href="#Ajax概述" class="headerlink" title="Ajax概述"></a>Ajax概述</h3><p>接下来就可以实现“好评”和“差评”的功能了，很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验，因此我们考虑使用<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/AJAX">Ajax</a>技术来实现“好评”和“差评”。Ajax是Asynchronous Javascript And XML的缩写 , 简单的说，使用Ajax技术可以在不重新加载整个页面的情况下对页面进行局部刷新。</p>
<p>对于传统的Web应用，每次页面上需要加载新的内容都需要重新请求服务器并刷新整个页面，如果服务器短时间内无法给予响应或者网络状况并不理想，那么可能会造成浏览器长时间的空白并使得用户处于等待状态，在这个期间用户什么都做不了，如下图所示。很显然，这样的Web应用并不能带来很好的用户体验。</p>
<p><img src="http://iubest.gitee.io/pic/synchronous-web-request.png" alt="img"></p>
<p>对于使用Ajax技术的Web应用，浏览器可以向服务器发起异步请求来获取数据。异步请求不会中断用户体验，当服务器返回了新的数据，我们可以通过JavaScript代码进行DOM操作来实现对页面的局部刷新，这样就相当于在不刷新整个页面的情况下更新了页面的内容，如下图所示。</p>
<p><img src="http://iubest.gitee.io/pic/asynchronous-web-request.png" alt="img"></p>
<p>在使用Ajax技术时，浏览器跟服务器通常会交换XML或JSON格式的数据，XML是以前使用得非常多的一种数据格式，近年来几乎已经完全被JSON取代，下面是两种数据格式的对比。</p>
<p>XML格式：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">message</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">from</span>&gt;</span>Alice<span class="tag">&lt;/<span class="name">from</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">to</span>&gt;</span>Bob<span class="tag">&lt;/<span class="name">to</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">content</span>&gt;</span>Dinner is on me!<span class="tag">&lt;/<span class="name">content</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">message</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>JSON格式：</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">&quot;from&quot;</span>: <span class="string">&quot;Alice&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;to&quot;</span>: <span class="string">&quot;Bob&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;content&quot;</span>: <span class="string">&quot;Dinner is on me!&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>通过上面的对比，明显JSON格式的数据要紧凑得多，所以传输效率更高，而且JSON本身也是JavaScript中的一种对象表达式语法，在JavaScript代码中处理JSON格式的数据更加方便。</p>
<h3 id="用Ajax实现投票功能"><a href="#用Ajax实现投票功能" class="headerlink" title="用Ajax实现投票功能"></a>用Ajax实现投票功能</h3><p>下面，我们使用Ajax技术来实现投票的功能，首先修改项目的<code>urls.py</code>文件，为“好评”和“差评”功能映射对应的URL。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.contrib <span class="keyword">import</span> admin</span><br><span class="line"><span class="keyword">from</span> django.urls <span class="keyword">import</span> path</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> vote <span class="keyword">import</span> views</span><br><span class="line"></span><br><span class="line">urlpatterns = [</span><br><span class="line">    path(<span class="string">&#x27;&#x27;</span>, views.show_subjects),</span><br><span class="line">    path(<span class="string">&#x27;teachers/&#x27;</span>, views.show_teachers),</span><br><span class="line">    path(<span class="string">&#x27;praise/&#x27;</span>, views.prise_or_criticize),</span><br><span class="line">    path(<span class="string">&#x27;criticize/&#x27;</span>, views.prise_or_criticize),</span><br><span class="line">    path(<span class="string">&#x27;admin/&#x27;</span>, admin.site.urls),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>设计视图函数<code>praise_or_criticize</code>来支持“好评”和“差评”功能，该视图函数通过Django封装的JsonResponse类将字典序列化成JSON字符串作为返回给浏览器的响应内容。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">praise_or_criticize</span>(<span class="params">request</span>):</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;好评&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        tno = int(request.GET.get(<span class="string">&#x27;tno&#x27;</span>))</span><br><span class="line">        teacher = Teacher.objects.get(no=tno)</span><br><span class="line">        <span class="keyword">if</span> request.path.startswith(<span class="string">&#x27;/praise&#x27;</span>):</span><br><span class="line">            teacher.good_count += <span class="number">1</span></span><br><span class="line">            count = teacher.good_count</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            teacher.bad_count += <span class="number">1</span></span><br><span class="line">            count = teacher.bad_count</span><br><span class="line">        teacher.save()</span><br><span class="line">        data = &#123;<span class="string">&#x27;code&#x27;</span>: <span class="number">20000</span>, <span class="string">&#x27;mesg&#x27;</span>: <span class="string">&#x27;操作成功&#x27;</span>, <span class="string">&#x27;count&#x27;</span>: count&#125;</span><br><span class="line">    <span class="keyword">except</span> (ValueError, Teacher.DoseNotExist):</span><br><span class="line">        data = &#123;<span class="string">&#x27;code&#x27;</span>: <span class="number">20001</span>, <span class="string">&#x27;mesg&#x27;</span>: <span class="string">&#x27;操作失败&#x27;</span>&#125;</span><br><span class="line">    <span class="keyword">return</span> JsonResponse(data)</span><br></pre></td></tr></table></figure>

<p>修改显示老师信息的模板页，引入jQuery库来实现事件处理、Ajax请求和DOM操作。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>老师信息<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="css">        <span class="selector-id">#container</span> &#123;</span></span><br><span class="line">            width: 80%;</span><br><span class="line">            margin: 10px auto;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.teacher</span> &#123;</span></span><br><span class="line">            width: 100%;</span><br><span class="line">            margin: 0 auto;</span><br><span class="line">            padding: 10px 0;</span><br><span class="line">            border-bottom: 1px dashed gray;</span><br><span class="line">            overflow: auto;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.teacher</span>&gt;<span class="selector-tag">div</span> &#123;</span></span><br><span class="line">            float: left;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.photo</span> &#123;</span></span><br><span class="line">            height: 140px;</span><br><span class="line">            border-radius: 75px;</span><br><span class="line">            overflow: hidden;</span><br><span class="line">            margin-left: 20px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.info</span> &#123;</span></span><br><span class="line">            width: 75%;</span><br><span class="line">            margin-left: 30px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.info</span> <span class="selector-tag">div</span> &#123;</span></span><br><span class="line">            clear: both;</span><br><span class="line">            margin: 5px 10px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.info</span> <span class="selector-tag">span</span> &#123;</span></span><br><span class="line">            margin-right: 25px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.info</span> <span class="selector-tag">a</span> &#123;</span></span><br><span class="line">            text-decoration: none;</span><br><span class="line">            color: darkcyan;</span><br><span class="line">        &#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;container&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;&#123; subject.name &#125;&#125;学科的老师信息<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">hr</span>&gt;</span></span><br><span class="line">        &#123;% if not teachers %&#125;</span><br><span class="line">            <span class="tag">&lt;<span class="name">h2</span>&gt;</span>暂无该学科老师信息<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span><br><span class="line">        &#123;% endif %&#125;</span><br><span class="line">        &#123;% for teacher in teachers %&#125;</span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;teacher&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;photo&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;/static/images/&#123;&#123; teacher.photo &#125;&#125;&quot;</span> <span class="attr">height</span>=<span class="string">&quot;140&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;info&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">span</span>&gt;</span><span class="tag">&lt;<span class="name">strong</span>&gt;</span>姓名：&#123;&#123; teacher.name &#125;&#125;<span class="tag">&lt;/<span class="name">strong</span>&gt;</span><span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">span</span>&gt;</span>性别：&#123;&#123; teacher.sex | yesno:&#x27;男,女&#x27; &#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">span</span>&gt;</span>出生日期：&#123;&#123; teacher.birth &#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;intro&quot;</span>&gt;</span>&#123;&#123; teacher.intro &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;comment&quot;</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/praise/?tno=&#123;&#123; teacher.no &#125;&#125;&quot;</span>&gt;</span>好评<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span></span><br><span class="line">                    (<span class="tag">&lt;<span class="name">strong</span>&gt;</span>&#123;&#123; teacher.good_count &#125;&#125;<span class="tag">&lt;/<span class="name">strong</span>&gt;</span>)</span><br><span class="line">                    <span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/criticize/?tno=&#123;&#123; teacher.no &#125;&#125;&quot;</span>&gt;</span>差评<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span></span><br><span class="line">                    (<span class="tag">&lt;<span class="name">strong</span>&gt;</span>&#123;&#123; teacher.bad_count &#125;&#125;<span class="tag">&lt;/<span class="name">strong</span>&gt;</span>)</span><br><span class="line">                <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        &#123;% endfor %&#125;</span><br><span class="line">        <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/&quot;</span>&gt;</span>返回首页<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript">        $(() =&gt; &#123;</span></span><br><span class="line"><span class="javascript">            $(<span class="string">&#x27;.comment&gt;a&#x27;</span>).on(<span class="string">&#x27;click&#x27;</span>, <span class="function">(<span class="params">evt</span>) =&gt;</span> &#123;</span></span><br><span class="line">                evt.preventDefault()</span><br><span class="line"><span class="javascript">                <span class="keyword">let</span> url = $(evt.target).attr(<span class="string">&#x27;href&#x27;</span>)</span></span><br><span class="line"><span class="javascript">                $.getJSON(url, <span class="function">(<span class="params">json</span>) =&gt;</span> &#123;</span></span><br><span class="line">                    if (json.code == 20000) &#123;</span><br><span class="line"><span class="javascript">                        $(evt.target).next().text(json.count)</span></span><br><span class="line"><span class="javascript">                    &#125; <span class="keyword">else</span> &#123;</span></span><br><span class="line">                        alert(json.mesg)</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;)</span><br><span class="line">        &#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>上面的前端代码中，使用了jQuery库封装的<code>getJSON</code>方法向服务器发送异步请求，如果不熟悉前端的jQuery库，可以参考<a target="_blank" rel="noopener" href="https://www.runoob.com/manual/jquery/">《jQuery API手册》</a>。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>到此为止，这个投票项目的核心功能已然完成，在下面的章节中我们会要求用户必须登录才能投票，没有账号的用户可以通过注册功能注册一个账号。�必须登录才能投票，没有账号的用户可以通过注册功能注册一个账号。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/django/" rel="tag">django</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-django/5-制作报表"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/django/5-%E5%88%B6%E4%BD%9C%E6%8A%A5%E8%A1%A8/"
    >5-制作报表.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/django/5-%E5%88%B6%E4%BD%9C%E6%8A%A5%E8%A1%A8/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/django/">django</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="制作报表"><a href="#制作报表" class="headerlink" title="制作报表"></a>制作报表</h2><h3 id="导出Excel报表"><a href="#导出Excel报表" class="headerlink" title="导出Excel报表"></a>导出Excel报表</h3><p>报表就是用表格、图表等格式来动态显示数据，所以有人用这样的公式来描述报表：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">报表 &#x3D; 多样的格式 + 动态的数据</span><br></pre></td></tr></table></figure>

<p>有很多的三方库支持在Python程序中写Excel文件，包括<a target="_blank" rel="noopener" href="https://xlwt.readthedocs.io/en/latest/"><code>xlwt</code></a>、<a target="_blank" rel="noopener" href="https://docs.xlwings.org/en/latest/quickstart.html"><code>xlwings</code></a>、<a target="_blank" rel="noopener" href="https://openpyxl.readthedocs.io/en/latest/"><code>openpyxl</code></a>、<a target="_blank" rel="noopener" href="https://xlsxwriter.readthedocs.io/"><code>xlswriter</code></a>等，其中的xlwt虽然只支持写xls格式的Excel文件，但在性能方面的表现还是不错的。下面我们就以<code>xlwt</code>为例，来演示如何在Django项目中导出Excel报表。</p>
<p>安装<code>xlwt</code>。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install xlwt</span><br></pre></td></tr></table></figure>

<p>导出包含所有老师信息的Excel表格的视图函数。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">export_teachers_excel</span>(<span class="params">request</span>):</span></span><br><span class="line">    <span class="comment"># 创建工作簿</span></span><br><span class="line">    wb = xlwt.Workbook()</span><br><span class="line">    <span class="comment"># 添加工作表</span></span><br><span class="line">    sheet = wb.add_sheet(<span class="string">&#x27;老师信息表&#x27;</span>)</span><br><span class="line">    <span class="comment"># 查询所有老师的信息</span></span><br><span class="line">    queryset = Teacher.objects.all()</span><br><span class="line">    <span class="comment"># 向Excel表单中写入表头</span></span><br><span class="line">    colnames = (<span class="string">&#x27;姓名&#x27;</span>, <span class="string">&#x27;介绍&#x27;</span>, <span class="string">&#x27;好评数&#x27;</span>, <span class="string">&#x27;差评数&#x27;</span>, <span class="string">&#x27;学科&#x27;</span>)</span><br><span class="line">    <span class="keyword">for</span> index, name <span class="keyword">in</span> enumerate(colnames):</span><br><span class="line">        sheet.write(<span class="number">0</span>, index, name)</span><br><span class="line">    <span class="comment"># 向单元格中写入老师的数据</span></span><br><span class="line">    props = (<span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;detail&#x27;</span>, <span class="string">&#x27;good_count&#x27;</span>, <span class="string">&#x27;bad_count&#x27;</span>, <span class="string">&#x27;subject&#x27;</span>)</span><br><span class="line">    <span class="keyword">for</span> row, teacher <span class="keyword">in</span> enumerate(queryset):</span><br><span class="line">        <span class="keyword">for</span> col, prop <span class="keyword">in</span> enumerate(props):</span><br><span class="line">            value = getattr(teacher, prop, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">            <span class="keyword">if</span> isinstance(value, Subject):</span><br><span class="line">                value = value.name</span><br><span class="line">            sheet.write(row + <span class="number">1</span>, col, value)</span><br><span class="line">    <span class="comment"># 保存Excel</span></span><br><span class="line">    buffer = BytesIO()</span><br><span class="line">    wb.save(buffer)</span><br><span class="line">    <span class="comment"># 将二进制数据写入响应的消息体中并设置MIME类型</span></span><br><span class="line">    resp = HttpResponse(buffer.getvalue(), content_type=<span class="string">&#x27;application/vnd.ms-excel&#x27;</span>)</span><br><span class="line">    <span class="comment"># 中文文件名需要处理成百分号编码</span></span><br><span class="line">    filename = quote(<span class="string">&#x27;老师.xls&#x27;</span>)</span><br><span class="line">    <span class="comment"># 通过响应头告知浏览器下载该文件以及对应的文件名</span></span><br><span class="line">    resp[<span class="string">&#x27;content-disposition&#x27;</span>] = <span class="string">f&#x27;attachment; filename*=utf-8&#x27;</span><span class="string">&#x27;&#123;filename&#125;&#x27;</span></span><br><span class="line">    <span class="keyword">return</span> resp</span><br></pre></td></tr></table></figure>

<p>映射URL。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">urlpatterns = [</span><br><span class="line">    </span><br><span class="line">    path(<span class="string">&#x27;excel/&#x27;</span>, views.export_teachers_excel),</span><br><span class="line">    </span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<h3 id="导出PDF报表"><a href="#导出PDF报表" class="headerlink" title="导出PDF报表"></a>导出PDF报表</h3><p>在Django项目中，如果需要导出PDF报表，可以借助三方库<code>reportlab</code>来生成PDF文件的内容，再将文件的二进制数据输出给浏览器并指定MIME类型为<code>application/pdf</code>，具体的代码如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">export_pdf</span>(<span class="params">request: HttpRequest</span>) -&gt; HttpResponse:</span></span><br><span class="line">    buffer = io.BytesIO()</span><br><span class="line">    pdf = canvas.Canvas(buffer)</span><br><span class="line">    pdf.setFont(<span class="string">&quot;Helvetica&quot;</span>, <span class="number">80</span>)</span><br><span class="line">    pdf.setFillColorRGB(<span class="number">0.2</span>, <span class="number">0.5</span>, <span class="number">0.3</span>)</span><br><span class="line">    pdf.drawString(<span class="number">100</span>, <span class="number">550</span>, <span class="string">&#x27;hello, world!&#x27;</span>)</span><br><span class="line">    pdf.showPage()</span><br><span class="line">    pdf.save()</span><br><span class="line">    resp = HttpResponse(buffer.getvalue(), content_type=<span class="string">&#x27;application/pdf&#x27;</span>)</span><br><span class="line">    resp[<span class="string">&#x27;content-disposition&#x27;</span>] = <span class="string">&#x27;inline; filename=&quot;demo.pdf&quot;&#x27;</span></span><br><span class="line">    <span class="keyword">return</span> resp</span><br></pre></td></tr></table></figure>

<p>关于如何用<code>reportlab</code>定制PDF报表的内容，可以参考reportlab的<a target="_blank" rel="noopener" href="https://www.reportlab.com/docs/reportlab-userguide.pdf">官方文档</a>。</p>
<h3 id="生成前端统计图表"><a href="#生成前端统计图表" class="headerlink" title="生成前端统计图表"></a>生成前端统计图表</h3><p>如果项目中需要生成前端统计图表，可以使用百度的<a target="_blank" rel="noopener" href="https://echarts.baidu.com/">ECharts</a>。具体的做法是后端通过提供数据接口返回统计图表所需的数据，前端使用ECharts来渲染出柱状图、折线图、饼图、散点图等图表。例如我们要生成一个统计所有老师好评数和差评数的报表，可以按照下面的方式来做。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_teachers_data</span>(<span class="params">request</span>):</span></span><br><span class="line">    queryset = Teacher.objects.all()</span><br><span class="line">    names = [teacher.name <span class="keyword">for</span> teacher <span class="keyword">in</span> queryset]</span><br><span class="line">    good_counts = [teacher.good_count <span class="keyword">for</span> teacher <span class="keyword">in</span> queryset]</span><br><span class="line">    bad_counts = [teacher.bad_count <span class="keyword">for</span> teacher <span class="keyword">in</span> queryset]</span><br><span class="line">    <span class="keyword">return</span> JsonResponse(&#123;<span class="string">&#x27;names&#x27;</span>: names, <span class="string">&#x27;good&#x27;</span>: good_counts, <span class="string">&#x27;bad&#x27;</span>: bad_counts&#125;)</span><br></pre></td></tr></table></figure>

<p>映射URL。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">urlpatterns = [</span><br><span class="line">    path(<span class="string">&#x27;teachers_data/&#x27;</span>, views.get_teachers_data),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>使用ECharts生成柱状图。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>老师评价统计<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;main&quot;</span> <span class="attr">style</span>=<span class="string">&quot;width: 600px; height: 400px&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/&quot;</span>&gt;</span>返回首页<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn.bootcss.com/echarts/4.2.1-rc1/echarts.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript">        <span class="keyword">var</span> myChart = echarts.init(<span class="built_in">document</span>.querySelector(<span class="string">&#x27;#main&#x27;</span>))</span></span><br><span class="line"><span class="javascript">        fetch(<span class="string">&#x27;/teachers_data/&#x27;</span>)</span></span><br><span class="line"><span class="javascript">            .then(<span class="function"><span class="params">resp</span> =&gt;</span> resp.json())</span></span><br><span class="line"><span class="javascript">            .then(<span class="function"><span class="params">json</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">                <span class="keyword">var</span> option = &#123;</span></span><br><span class="line"><span class="javascript">                    color: [<span class="string">&#x27;#f00&#x27;</span>, <span class="string">&#x27;#00f&#x27;</span>],</span></span><br><span class="line">                    title: &#123;</span><br><span class="line"><span class="javascript">                        text: <span class="string">&#x27;老师评价统计图&#x27;</span></span></span><br><span class="line">                    &#125;,</span><br><span class="line">                    tooltip: &#123;&#125;,</span><br><span class="line">                    legend: &#123;</span><br><span class="line"><span class="javascript">                        data:[<span class="string">&#x27;好评&#x27;</span>, <span class="string">&#x27;差评&#x27;</span>]</span></span><br><span class="line">                    &#125;,</span><br><span class="line">                    xAxis: &#123;</span><br><span class="line">                        data: json.names</span><br><span class="line">                    &#125;,</span><br><span class="line">                    yAxis: &#123;&#125;,</span><br><span class="line">                    series: [</span><br><span class="line">                        &#123;</span><br><span class="line"><span class="javascript">                            name: <span class="string">&#x27;好评&#x27;</span>,</span></span><br><span class="line"><span class="javascript">                            type: <span class="string">&#x27;bar&#x27;</span>,</span></span><br><span class="line">                            data: json.good</span><br><span class="line">                        &#125;,</span><br><span class="line">                        &#123;</span><br><span class="line"><span class="javascript">                            name: <span class="string">&#x27;差评&#x27;</span>,</span></span><br><span class="line"><span class="javascript">                            type: <span class="string">&#x27;bar&#x27;</span>,</span></span><br><span class="line">                            data: json.bad</span><br><span class="line">                        &#125;</span><br><span class="line">                    ]</span><br><span class="line">                &#125;</span><br><span class="line">                myChart.setOption(option)</span><br><span class="line">            &#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>运行效果如下图所示。</p>
<p><img src="http://iubest.gitee.io/pic/echarts_bar_graph.png" alt="img"></p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/django/" rel="tag">django</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-django/6-日志和调试工具栏"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/django/6-%E6%97%A5%E5%BF%97%E5%92%8C%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7%E6%A0%8F/"
    >6-日志和调试工具栏.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/django/6-%E6%97%A5%E5%BF%97%E5%92%8C%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7%E6%A0%8F/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/django/">django</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="日志和调试工具栏"><a href="#日志和调试工具栏" class="headerlink" title="日志和调试工具栏"></a>日志和调试工具栏</h2><h3 id="配置日志"><a href="#配置日志" class="headerlink" title="配置日志"></a>配置日志</h3><p>项目开发阶段，显示足够的调试信息以辅助开发人员调试代码还是非常必要的；项目上线以后，将系统运行时出现的警告、错误等信息记录下来以备相关人员了解系统运行状况并维护代码也是很有必要的。与此同时，采集日志数据也是为网站做数字化运营奠定一个基础，通过对系统运行日志的分析，我们可以监测网站的流量以及流量分布，同时还可以挖掘出用户的使用习惯和行为模式。</p>
<p>接下来，我们先看看如何通过Django的配置文件来配置日志。Django的日志配置基本可以参照官方文档再结合项目实际需求来进行，这些内容基本上可以从官方文档上复制下来，然后进行局部的调整即可，下面给出一些参考配置。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line">LOGGING = &#123;</span><br><span class="line">    <span class="string">&#x27;version&#x27;</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="comment"># 是否禁用已经存在的日志器</span></span><br><span class="line">    <span class="string">&#x27;disable_existing_loggers&#x27;</span>: <span class="literal">False</span>,</span><br><span class="line">    <span class="comment"># 日志格式化器</span></span><br><span class="line">    <span class="string">&#x27;formatters&#x27;</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;simple&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;format&#x27;</span>: <span class="string">&#x27;%(asctime)s %(module)s.%(funcName)s: %(message)s&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;datefmt&#x27;</span>: <span class="string">&#x27;%Y-%m-%d %H:%M:%S&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="string">&#x27;verbose&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;format&#x27;</span>: <span class="string">&#x27;%(asctime)s %(levelname)s [%(process)d-%(threadName)s] &#x27;</span></span><br><span class="line">                      <span class="string">&#x27;%(module)s.%(funcName)s line %(lineno)d: %(message)s&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;datefmt&#x27;</span>: <span class="string">&#x27;%Y-%m-%d %H:%M:%S&#x27;</span>,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment"># 日志过滤器</span></span><br><span class="line">    <span class="string">&#x27;filters&#x27;</span>: &#123;</span><br><span class="line">        <span class="comment"># 只有在Django配置文件中DEBUG值为True时才起作用</span></span><br><span class="line">        <span class="string">&#x27;require_debug_true&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;()&#x27;</span>: <span class="string">&#x27;django.utils.log.RequireDebugTrue&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment"># 日志处理器</span></span><br><span class="line">    <span class="string">&#x27;handlers&#x27;</span>: &#123;</span><br><span class="line">        <span class="comment"># 输出到控制台</span></span><br><span class="line">        <span class="string">&#x27;console&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;class&#x27;</span>: <span class="string">&#x27;logging.StreamHandler&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;level&#x27;</span>: <span class="string">&#x27;DEBUG&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;filters&#x27;</span>: [<span class="string">&#x27;require_debug_true&#x27;</span>],</span><br><span class="line">            <span class="string">&#x27;formatter&#x27;</span>: <span class="string">&#x27;simple&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="comment"># 输出到文件(每周切割一次)</span></span><br><span class="line">        <span class="string">&#x27;file1&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;class&#x27;</span>: <span class="string">&#x27;logging.handlers.TimedRotatingFileHandler&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;filename&#x27;</span>: <span class="string">&#x27;access.log&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;when&#x27;</span>: <span class="string">&#x27;W0&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;backupCount&#x27;</span>: <span class="number">12</span>,</span><br><span class="line">            <span class="string">&#x27;formatter&#x27;</span>: <span class="string">&#x27;simple&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;level&#x27;</span>: <span class="string">&#x27;INFO&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="comment"># 输出到文件(每天切割一次)</span></span><br><span class="line">        <span class="string">&#x27;file2&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;class&#x27;</span>: <span class="string">&#x27;logging.handlers.TimedRotatingFileHandler&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;filename&#x27;</span>: <span class="string">&#x27;error.log&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;when&#x27;</span>: <span class="string">&#x27;D&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;backupCount&#x27;</span>: <span class="number">31</span>,</span><br><span class="line">            <span class="string">&#x27;formatter&#x27;</span>: <span class="string">&#x27;verbose&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;level&#x27;</span>: <span class="string">&#x27;WARNING&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment"># 日志器记录器</span></span><br><span class="line">    <span class="string">&#x27;loggers&#x27;</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;django&#x27;</span>: &#123;</span><br><span class="line">            <span class="comment"># 需要使用的日志处理器</span></span><br><span class="line">            <span class="string">&#x27;handlers&#x27;</span>: [<span class="string">&#x27;console&#x27;</span>, <span class="string">&#x27;file1&#x27;</span>, <span class="string">&#x27;file2&#x27;</span>],</span><br><span class="line">            <span class="comment"># 是否向上传播日志信息</span></span><br><span class="line">            <span class="string">&#x27;propagate&#x27;</span>: <span class="literal">True</span>,</span><br><span class="line">            <span class="comment"># 日志级别(不一定是最终的日志级别)</span></span><br><span class="line">            <span class="string">&#x27;level&#x27;</span>: <span class="string">&#x27;DEBUG&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>大家可能已经注意到了，上面日志配置中的<code>formatters</code>是<strong>日志格式化器</strong>，它代表了如何格式化输出日志，其中格式占位符分别表示：</p>
<ol>
<li><code>%(name)s</code> - 记录器的名称</li>
<li><code>%(levelno)s</code> - 数字形式的日志记录级别</li>
<li><code>%(levelname)s</code> - 日志记录级别的文本名称</li>
<li><code>%(filename)s</code> - 执行日志记录调用的源文件的文件名称</li>
<li><code>%(pathname)s</code> - 执行日志记录调用的源文件的路径名称</li>
<li><code>%(funcName)s</code> - 执行日志记录调用的函数名称</li>
<li><code>%(module)s</code> - 执行日志记录调用的模块名称</li>
<li><code>%(lineno)s</code> - 执行日志记录调用的行号</li>
<li><code>%(created)s</code> - 执行日志记录的时间</li>
<li><code>%(asctime)s</code> - 日期和时间</li>
<li><code>%(msecs)s</code> - 毫秒部分</li>
<li><code>%(thread)d</code> - 线程ID（整数）</li>
<li><code>%(threadName)s</code> - 线程名称</li>
<li><code>%(process)d</code> - 进程ID （整数）</li>
</ol>
<p>日志配置中的handlers用来指定<strong>日志处理器</strong>，简单的说就是指定将日志输出到控制台还是文件又或者是网络上的服务器，可用的处理器包括：</p>
<ol>
<li><code>logging.StreamHandler(stream=None)</code> - 可以向类似与<code>sys.stdout</code>或者<code>sys.stderr</code>的任何文件对象输出信息</li>
<li><code>logging.FileHandler(filename, mode=&#39;a&#39;, encoding=None, delay=False)</code> - 将日志消息写入文件</li>
<li><code>logging.handlers.DatagramHandler(host, port)</code> - 使用UDP协议，将日志信息发送到指定主机和端口的网络主机上</li>
<li><code>logging.handlers.HTTPHandler(host, url)</code> - 使用HTTP的GET或POST方法将日志消息上传到一台HTTP 服务器</li>
<li><code>logging.handlers.RotatingFileHandler(filename, mode=&#39;a&#39;, maxBytes=0, backupCount=0, encoding=None, delay=False)</code> - 将日志消息写入文件，如果文件的大小超出<code>maxBytes</code>指定的值，那么将重新生成一个文件来记录日志</li>
<li><code>logging.handlers.SocketHandler(host, port)</code> - 使用TCP协议，将日志信息发送到指定主机和端口的网络主机上 </li>
<li><code>logging.handlers.SMTPHandler(mailhost, fromaddr, toaddrs, subject, credentials=None, secure=None, timeout=1.0)</code> - 将日志输出到指定的邮件地址</li>
<li><code>logging.MemoryHandler(capacity, flushLevel=ERROR, target=None, flushOnClose=True)</code> - 将日志输出到内存指定的缓冲区中</li>
</ol>
<p>上面每个日志处理器都指定了一个名为<code>level</code>的属性，它代表了日志的级别，不同的日志级别反映出日志中记录信息的严重性。Python中定义了六个级别的日志，按照从低到高的顺序依次是：NOTSET、DEBUG、INFO、WARNING、ERROR、CRITICAL。</p>
<p>最后配置的<strong>日志记录器</strong>是用来真正输出日志的，Django框架提供了如下所示的内置记录器：</p>
<ol>
<li><code>django</code> - 在Django层次结构中的所有消息记录器</li>
<li><code>django.request</code> - 与请求处理相关的日志消息。5xx响应被视为错误消息；4xx响应被视为为警告消息</li>
<li><code>django.server</code> - 与通过runserver调用的服务器所接收的请求相关的日志消息。5xx响应被视为错误消息；4xx响应被记录为警告消息；其他一切都被记录为INFO</li>
<li><code>django.template</code> - 与模板渲染相关的日志消息</li>
<li><code>django.db.backends</code> - 有与数据库交互产生的日志消息，如果希望显示ORM框架执行的SQL语句，就可以使用该日志记录器。</li>
</ol>
<p>日志记录器中配置的日志级别有可能不是最终的日志级别，因为还要参考日志处理器中配置的日志级别，取二者中级别较高者作为最终的日志级别。</p>
<h3 id="配置Django-Debug-Toolbar"><a href="#配置Django-Debug-Toolbar" class="headerlink" title="配置Django-Debug-Toolbar"></a>配置Django-Debug-Toolbar</h3><p>如果想调试你的Django项目，你一定不能不过名为Django-Debug-Toolbar的神器，它是项目开发阶段辅助调试和优化的必备工具，只要配置了它，就可以很方便的查看到如下表所示的项目运行信息，这些信息对调试项目和优化Web应用性能都是至关重要的。</p>
<table>
<thead>
<tr>
<th>项目</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>Versions</td>
<td>Django的版本</td>
</tr>
<tr>
<td>Time</td>
<td>显示视图耗费的时间</td>
</tr>
<tr>
<td>Settings</td>
<td>配置文件中设置的值</td>
</tr>
<tr>
<td>Headers</td>
<td>HTTP请求头和响应头的信息</td>
</tr>
<tr>
<td>Request</td>
<td>和请求相关的各种变量及其信息</td>
</tr>
<tr>
<td>StaticFiles</td>
<td>静态文件加载情况</td>
</tr>
<tr>
<td>Templates</td>
<td>模板的相关信息</td>
</tr>
<tr>
<td>Cache</td>
<td>缓存的使用情况</td>
</tr>
<tr>
<td>Signals</td>
<td>Django内置的信号信息</td>
</tr>
<tr>
<td>Logging</td>
<td>被记录的日志信息</td>
</tr>
<tr>
<td>SQL</td>
<td>向数据库发送的SQL语句及其执行时间</td>
</tr>
</tbody></table>
<ol>
<li><p>安装Django-Debug-Toolbar。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install django-debug-toolbar</span><br></pre></td></tr></table></figure>
</li>
<li><p>配置 - 修改settings.py。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">INSTALLED_APPS = [</span><br><span class="line">    <span class="string">&#x27;debug_toolbar&#x27;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">MIDDLEWARE = [</span><br><span class="line">    <span class="string">&#x27;debug_toolbar.middleware.DebugToolbarMiddleware&#x27;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">DEBUG_TOOLBAR_CONFIG = &#123;</span><br><span class="line">    <span class="comment"># 引入jQuery库</span></span><br><span class="line">    <span class="string">&#x27;JQUERY_URL&#x27;</span>: <span class="string">&#x27;https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js&#x27;</span>,</span><br><span class="line">    <span class="comment"># 工具栏是否折叠</span></span><br><span class="line">    <span class="string">&#x27;SHOW_COLLAPSED&#x27;</span>: <span class="literal">True</span>,</span><br><span class="line">    <span class="comment"># 是否显示工具栏</span></span><br><span class="line">    <span class="string">&#x27;SHOW_TOOLBAR_CALLBACK&#x27;</span>: <span class="keyword">lambda</span> x: <span class="literal">True</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>配置 - 修改urls.py。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> settings.DEBUG:</span><br><span class="line"></span><br><span class="line">    <span class="keyword">import</span> debug_toolbar</span><br><span class="line"></span><br><span class="line">    urlpatterns.insert(<span class="number">0</span>, path(<span class="string">&#x27;__debug__/&#x27;</span>, include(debug_toolbar.urls)))</span><br></pre></td></tr></table></figure>
</li>
<li><p>在配置好Django-Debug-Toolbar之后，页面右侧会看到一个调试工具栏，如下图所示，上面包括了如前所述的各种调试信息，包括执行时间、项目设置、请求头、SQL、静态资源、模板、缓存、信号等，查看起来非常的方便。</p>
<p><img src="http://iubest.gitee.io/pic/debug-toolbar.png" alt="img"></p>
</li>
</ol>
<h3 id="优化ORM代码"><a href="#优化ORM代码" class="headerlink" title="优化ORM代码"></a>优化ORM代码</h3><p>在配置了日志或Django-Debug-Toolbar之后，我们可以查看一下之前将老师数据导出成Excel报表的视图函数执行情况，这里我们关注的是ORM框架生成的SQL查询到底是什么样子的，相信这里的结果会让你感到有一些意外。执行<code>Teacher.objects.all()</code>之后我们可以注意到，在控制台看到的或者通过Django-Debug-Toolbar输出的SQL是下面这样的：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="string">`tb_teacher`</span>.<span class="string">`no`</span>, <span class="string">`tb_teacher`</span>.<span class="string">`name`</span>, <span class="string">`tb_teacher`</span>.<span class="string">`detail`</span>, <span class="string">`tb_teacher`</span>.<span class="string">`photo`</span>, <span class="string">`tb_teacher`</span>.<span class="string">`good_count`</span>, <span class="string">`tb_teacher`</span>.<span class="string">`bad_count`</span>, <span class="string">`tb_teacher`</span>.<span class="string">`sno`</span> <span class="keyword">FROM</span> <span class="string">`tb_teacher`</span>; args=()</span><br><span class="line"><span class="keyword">SELECT</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span>, <span class="string">`tb_subject`</span>.<span class="string">`name`</span>, <span class="string">`tb_subject`</span>.<span class="string">`intro`</span>, <span class="string">`tb_subject`</span>.<span class="string">`create_date`</span>, <span class="string">`tb_subject`</span>.<span class="string">`is_hot`</span> <span class="keyword">FROM</span> <span class="string">`tb_subject`</span> <span class="keyword">WHERE</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span> = <span class="number">101</span>; args=(101,)</span><br><span class="line"><span class="keyword">SELECT</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span>, <span class="string">`tb_subject`</span>.<span class="string">`name`</span>, <span class="string">`tb_subject`</span>.<span class="string">`intro`</span>, <span class="string">`tb_subject`</span>.<span class="string">`create_date`</span>, <span class="string">`tb_subject`</span>.<span class="string">`is_hot`</span> <span class="keyword">FROM</span> <span class="string">`tb_subject`</span> <span class="keyword">WHERE</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span> = <span class="number">101</span>; args=(101,)</span><br><span class="line"><span class="keyword">SELECT</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span>, <span class="string">`tb_subject`</span>.<span class="string">`name`</span>, <span class="string">`tb_subject`</span>.<span class="string">`intro`</span>, <span class="string">`tb_subject`</span>.<span class="string">`create_date`</span>, <span class="string">`tb_subject`</span>.<span class="string">`is_hot`</span> <span class="keyword">FROM</span> <span class="string">`tb_subject`</span> <span class="keyword">WHERE</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span> = <span class="number">101</span>; args=(101,)</span><br><span class="line"><span class="keyword">SELECT</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span>, <span class="string">`tb_subject`</span>.<span class="string">`name`</span>, <span class="string">`tb_subject`</span>.<span class="string">`intro`</span>, <span class="string">`tb_subject`</span>.<span class="string">`create_date`</span>, <span class="string">`tb_subject`</span>.<span class="string">`is_hot`</span> <span class="keyword">FROM</span> <span class="string">`tb_subject`</span> <span class="keyword">WHERE</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span> = <span class="number">101</span>; args=(101,)</span><br><span class="line"><span class="keyword">SELECT</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span>, <span class="string">`tb_subject`</span>.<span class="string">`name`</span>, <span class="string">`tb_subject`</span>.<span class="string">`intro`</span>, <span class="string">`tb_subject`</span>.<span class="string">`create_date`</span>, <span class="string">`tb_subject`</span>.<span class="string">`is_hot`</span> <span class="keyword">FROM</span> <span class="string">`tb_subject`</span> <span class="keyword">WHERE</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span> = <span class="number">103</span>; args=(103,)</span><br><span class="line"><span class="keyword">SELECT</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span>, <span class="string">`tb_subject`</span>.<span class="string">`name`</span>, <span class="string">`tb_subject`</span>.<span class="string">`intro`</span>, <span class="string">`tb_subject`</span>.<span class="string">`create_date`</span>, <span class="string">`tb_subject`</span>.<span class="string">`is_hot`</span> <span class="keyword">FROM</span> <span class="string">`tb_subject`</span> <span class="keyword">WHERE</span> <span class="string">`tb_subject`</span>.<span class="string">`no`</span> = <span class="number">103</span>; args=(103,)</span><br></pre></td></tr></table></figure>

<p>这里的问题通常被称为“1+N查询”（有的地方也将其称之为“N+1查询”），原本获取老师的数据只需要一条SQL，但是由于老师关联了学科，当我们查询到<code>N</code>条老师的数据时，Django的ORM框架又向数据库发出了<code>N</code>条SQL去查询老师所属学科的信息。每条SQL执行都会有较大的开销而且会给数据库服务器带来压力，如果能够在一条SQL中完成老师和学科的查询肯定是更好的做法，这一点也很容易做到，相信大家已经想到怎么做了。是的，我们可以使用连接查询，但是在使用Django的ORM框架时如何做到这一点呢？对于多对一关联（如投票应用中的老师和学科），我们可以使用<code>QuerySet</code>的用<code>select_related()</code>方法来加载关联对象；而对于多对多关联（如电商网站中的订单和商品），我们可以使用<code>prefetch_related()</code>方法来加载关联对象。</p>
<p>在导出老师Excel报表的视图函数中，我们可以按照下面的方式优化代码。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">queryset = Teacher.objects.all().select_related(<span class="string">&#x27;subject&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>事实上，用ECharts生成前端报表的视图函数中，查询老师好评和差评数据的操作也能够优化，因为在这个例子中，我们只需要获取老师的姓名、好评数和差评数这三项数据，但是在默认的情况生成的SQL会查询老师表的所有字段。可以用<code>QuerySet</code>的<code>only()</code>方法来指定需要查询的属性，也可以用<code>QuerySet</code>的<code>defer()</code>方法来指定暂时不需要查询的属性，这样生成的SQL会通过投影操作来指定需要查询的列，从而改善查询性能，代码如下所示：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">queryset = Teacher.objects.all().only(<span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;good_count&#x27;</span>, <span class="string">&#x27;bad_count&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>当然，如果要统计出每个学科的老师好评和差评的平均数，利用Django的ORM框架也能够做到，代码如下所示：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">queryset = Teacher.objects.values(<span class="string">&#x27;subject&#x27;</span>).annotate(good=Avg(<span class="string">&#x27;good_count&#x27;</span>), bad=Avg(<span class="string">&#x27;bad_count&#x27;</span>))</span><br></pre></td></tr></table></figure>

<p>这里获得的<code>QuerySet</code>中的元素是字典对象，每个字典中有三组键值对，分别是代表学科编号的<code>subject</code>、代表好评数的<code>good</code>和代表差评数的<code>bad</code>。如果想要获得学科的名称而不是编号，可以按照如下所示的方式调整代码：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">queryset = Teacher.objects.values(<span class="string">&#x27;subject__name&#x27;</span>).annotate(good=Avg(<span class="string">&#x27;good_count&#x27;</span>), bad=Avg(<span class="string">&#x27;bad_count&#x27;</span>))</span><br></pre></td></tr></table></figure>

<p>可见，Django的ORM框架允许我们用面向对象的方式完成关系数据库中的分组和聚合查询。</p>
<p>见，Django的ORM框架允许我们用面向对象的方式完成关系数据库中的分组和聚合查询。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/django/" rel="tag">django</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-django/7-中间件的应用"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/django/7-%E4%B8%AD%E9%97%B4%E4%BB%B6%E7%9A%84%E5%BA%94%E7%94%A8/"
    >7-中间件的应用.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/django/7-%E4%B8%AD%E9%97%B4%E4%BB%B6%E7%9A%84%E5%BA%94%E7%94%A8/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/django/">django</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="中间件的应用"><a href="#中间件的应用" class="headerlink" title="中间件的应用"></a>中间件的应用</h2><p>之前我们已经实现了用户必须登录才能投票的限制，但是一个新的问题来了。如果我们的应用中有很多功能都需要用户先登录才能执行，例如将前面导出Excel报表和查看统计图表的功能都做了必须登录才能访问的限制，那么我们是不是需要在每个视图函数中添加代码来检查session中是否包含<code>userid</code>的代码呢？答案是否定的，如果这样做了，我们的视图函数中必然会充斥着大量的重复代码。编程大师<em>Martin Fowler</em>曾经说过：<strong>代码有很多种坏味道，重复是最坏的一种</strong>。在Python程序中，我们可以通过装饰器来为函数提供额外的能力；在Django项目中，我们可以把类似于验证用户是否登录这样的重复性代码放到<strong>中间件</strong>中。</p>
<h3 id="Django中间件概述"><a href="#Django中间件概述" class="headerlink" title="Django中间件概述"></a>Django中间件概述</h3><p>中间件是安插在Web应用请求和响应过程之间的组件，它在整个Web应用中扮演了拦截过滤器的角色，通过中间件可以拦截请求和响应，并对请求和响应进行过滤（简单的说就是执行额外的处理）。通常，一个中间件组件只专注于完成一件特定的事，例如：Django框架通过<code>SessionMiddleware</code>中间件实现了对session的支持，又通过<code>AuthenticationMiddleware</code>中间件实现了基于session的请求认证。通过把多个中间件组合在一起，我们可以完成更为复杂的任务，Django框架就是这么做的。</p>
<p>Django项目的配置文件中就包含了对中间件的配置，代码如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">MIDDLEWARE = [</span><br><span class="line">    <span class="string">&#x27;django.middleware.security.SecurityMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.contrib.sessions.middleware.SessionMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.middleware.common.CommonMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.middleware.csrf.CsrfViewMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.contrib.auth.middleware.AuthenticationMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.contrib.messages.middleware.MessageMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.middleware.clickjacking.XFrameOptionsMiddleware&#x27;</span>,</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>我们稍微为大家解释一下这些中间件的作用：</p>
<ol>
<li><code>CommonMiddleware</code> - 基础设置中间件，可以处理以下一些配置参数。<ul>
<li>DISALLOWED_USER_AGENTS - 不被允许的用户代理（浏览器）</li>
<li>APPEND_SLASH - 是否追加<code>/</code></li>
<li>USE_ETAG - 浏览器缓存相关</li>
</ul>
</li>
<li><code>SecurityMiddleware</code> - 安全相关中间件，可以处理和安全相关的配置项。<ul>
<li>SECURE_HSTS_SECONDS - 强制使用HTTPS的时间</li>
<li>SECURE_HSTS_INCLUDE_SUBDOMAINS - HTTPS是否覆盖子域名</li>
<li>SECURE_CONTENT_TYPE_NOSNIFF - 是否允许浏览器推断内容类型</li>
<li>SECURE_BROWSER_XSS_FILTER - 是否启用跨站脚本攻击过滤器</li>
<li>SECURE_SSL_REDIRECT - 是否重定向到HTTPS连接</li>
<li>SECURE_REDIRECT_EXEMPT - 免除重定向到HTTPS</li>
</ul>
</li>
<li><code>SessionMiddleware</code> - 会话中间件。</li>
<li><code>CsrfViewMiddleware</code> - 通过生成令牌，防范跨请求份伪的造中间件。</li>
<li><code>XFrameOptionsMiddleware</code> - 通过设置请求头参数，防范点击劫持攻击的中间件。</li>
</ol>
<p>在请求的过程中，上面的中间件会按照书写的顺序从上到下执行，然后是URL解析，最后请求才会来到视图函数；在响应的过程中，上面的中间件会按照书写的顺序从下到上执行，与请求时中间件执行的顺序正好相反。</p>
<h3 id="自定义中间件"><a href="#自定义中间件" class="headerlink" title="自定义中间件"></a>自定义中间件</h3><p>Django中的中间件有两种实现方式：基于类的实现方式和基于函数的实现方式，后者更接近于装饰器的写法。装饰器实际上是代理模式的应用，将横切关注功能（与正常业务逻辑没有必然联系的功能，例如：身份认证、日志记录、编码转换之类的功能）置于代理中，由代理对象来完成被代理对象的行为并添加额外的功能。中间件对用户请求和响应进行拦截过滤并增加额外的处理，在这一点上它跟装饰器是完全一致的，所以基于函数的写法来实现中间件就跟装饰器的写法几乎一模一样。下面我们用自定义的中间件来实现用户登录验证的功能。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">middlewares.py</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">from</span> django.http <span class="keyword">import</span> JsonResponse</span><br><span class="line"><span class="keyword">from</span> django.shortcuts <span class="keyword">import</span> redirect</span><br><span class="line"></span><br><span class="line"><span class="comment"># 需要登录才能访问的资源路径</span></span><br><span class="line">LOGIN_REQUIRED_URLS = &#123;<span class="string">&#x27;/praise/&#x27;</span>, <span class="string">&#x27;/criticize/&#x27;</span>, <span class="string">&#x27;/excel/&#x27;</span>, <span class="string">&#x27;/teachers_data/&#x27;</span>&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">check_login_middleware</span>(<span class="params">get_resp</span>):</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">wrapper</span>(<span class="params">request, *args, **kwargs</span>):</span></span><br><span class="line">        <span class="comment"># 请求的资源路径在上面的集合中</span></span><br><span class="line">        <span class="keyword">if</span> request.path <span class="keyword">in</span> LOGIN_REQUIRED_URLS:</span><br><span class="line">            <span class="comment"># 会话中包含userid则视为已经登录</span></span><br><span class="line">            <span class="keyword">if</span> <span class="string">&#x27;userid&#x27;</span> <span class="keyword">not</span> <span class="keyword">in</span> request.session:</span><br><span class="line">                <span class="comment"># 判断是不是Ajax请求</span></span><br><span class="line">                <span class="keyword">if</span> request.is_ajax():</span><br><span class="line">                    <span class="comment"># Ajax请求返回JSON数据提示用户登录</span></span><br><span class="line">                    <span class="keyword">return</span> JsonResponse(&#123;<span class="string">&#x27;code&#x27;</span>: <span class="number">10003</span>, <span class="string">&#x27;hint&#x27;</span>: <span class="string">&#x27;请先登录&#x27;</span>&#125;)</span><br><span class="line">                <span class="keyword">else</span>:</span><br><span class="line">                    backurl = request.get_full_path()</span><br><span class="line">                    <span class="comment"># 非Ajax请求直接重定向到登录页</span></span><br><span class="line">                    <span class="keyword">return</span> redirect(<span class="string">f&#x27;/login/?backurl=<span class="subst">&#123;backurl&#125;</span>&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> get_resp(request, *args, **kwargs)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> wrapper</span><br></pre></td></tr></table></figure>

<p>当然，我们也可以定义一个类来充当装饰器，如果类中有<code>__call__</code>魔术方法，这个类的对象就像函数一样可调用，所以下面是另一种实现中间件的方式，道理跟上面的代码完全一样。</p>
<p>还有一种基于类实现中间件的方式，这种方式在较新版本的Django中已经不推荐使用了，但是大家接触到的代码中，仍然有可能遇到这种写法，大致的代码如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.utils.deprecation <span class="keyword">import</span> MiddlewareMixin</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyMiddleware</span>(<span class="params">MiddlewareMixin</span>):</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">process_request</span>(<span class="params">self, request</span>):</span></span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">process_view</span>(<span class="params">self, request, view_func, view_args, view_kwargs</span>):</span></span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">process_template_response</span>(<span class="params">self, request, response</span>):</span></span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">process_response</span>(<span class="params">self, request, response</span>):</span></span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">process_exception</span>(<span class="params">self, request, exception</span>):</span></span><br><span class="line">        <span class="keyword">pass</span></span><br></pre></td></tr></table></figure>

<p>上面类中的五个方法都是中间件的钩子函数，分别在收到用户请求、进入视图函数之前、渲染模板、返回响应和出现异常的时候被回调。当然，写不写这些方法是根据中间件的需求来确定的，并不是所有的场景都需要重写五个方法，下面的图相信能够帮助大家理解这种写法。</p>
<p><img src="http://iubest.gitee.io/pic/django-middleware.png" alt="img"></p>
<p>写好中间件代码后，需要修改配置文件来激活中间件使其生效。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">MIDDLEWARE = [</span><br><span class="line">    <span class="string">&#x27;django.middleware.security.SecurityMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.contrib.sessions.middleware.SessionMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.middleware.common.CommonMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.middleware.csrf.CsrfViewMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.contrib.auth.middleware.AuthenticationMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.contrib.messages.middleware.MessageMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.middleware.clickjacking.XFrameOptionsMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;debug_toolbar.middleware.DebugToolbarMiddleware&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;vote.middlewares.check_login_middleware&#x27;</span>,</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>注意上面这个中间件列表中元素的顺序，当收到来自用户的请求时，中间件按照从上到下的顺序依次执行，这行完这些中间件以后，请求才会最终到达视图函数。当然，在这个过程中，用户的请求可以被拦截，就像上面我们自定义的中间件那样，如果用户在没有登录的情况下访问了受保护的资源，中间件会将请求直接重定向到登录页，后面的中间件和视图函数将不再执行。在响应用户请求的过程中，上面的中间件会按照从下到上的顺序依次执行，这样的话我们还可以对响应做进一步的处理。</p>
<p>中间件执行的顺序是非常重要的，对于有依赖关系的中间件必须保证被依赖的中间件要置于依赖它的中间件的前面，就好比我们刚才自定义的中间件要放到<code>SessionMiddleware</code>的后面，因为我们要依赖这个中间件为请求绑定的<code>session</code>对象才能判定用户是否登录。</p>
<p>�判定用户是否登录。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/django/" rel="tag">django</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-django/8-前后端分离开发入门"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/django/8-%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8/"
    >8-前后端分离开发入门.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/django/8-%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/django/">django</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="前后端分离开发入门"><a href="#前后端分离开发入门" class="headerlink" title="前后端分离开发入门"></a>前后端分离开发入门</h2><p>在传统的Web应用开发中，大多数的程序员会将浏览器作为前后端的分界线。将浏览器中为用户进行页面展示的部分称之为前端，而将运行在服务器为前端提供业务逻辑和数据准备的所有代码统称为后端。所谓前后端分离的开发，就是前后端工程师约定好数据交互接口，并行的进行开发和测试，后端只提供数据，不负责将数据渲染到页面上，前端通过HTTP请求获取数据并负责将数据渲染到页面上，这个工作是交给浏览器中的JavaScript代码来完成。</p>
<p>使用前后端分离开发有诸多的好处，下面我们简要的说下这些好处：</p>
<ol>
<li><strong>提升开发效率</strong>。前后端分离以后，可以实现前后端代码的解耦，只要前后端沟通约定好应用所需接口以及接口参数，便可以开始并行开发，无需等待对方的开发工作结束。在这种情况下，前后端工程师都可以只专注于自己的开发工作，有助于打造出更好的团队。除此之外，在前后端分离的开发模式下，即使需求发生变更，只要接口与数据格式不变，后端开发人员就不需要修改代码，只要前端进行变动即可。</li>
<li><strong>增强代码的可维护性</strong>。前后端分离后，应用的代码不再是前后端混合，只有在运行期才会有调用依赖关系，这样的话维护代码的工作将变得轻松愉快很多，再不会牵一发而动全身。当你的代码变得简明且整洁时，代码的可读性和可维护性都会有质的提升。</li>
<li><strong>支持多终端和服务化架构</strong>。前后端分离后，同一套数据接口可以为不同的终端提供服务，更有助于打造多终端应用；此外，由于后端提供的接口之间可以通过HTTP(S)进行调用，有助于打造服务化架构（包括微服务）。</li>
</ol>
<p>接下来我们就用前后端分离的方式来改写之前的投票应用。</p>
<h3 id="返回JSON格式的数据"><a href="#返回JSON格式的数据" class="headerlink" title="返回JSON格式的数据"></a>返回JSON格式的数据</h3><p>刚才说过，在前后端分离的开发模式下，后端需要为前端提供数据接口，这些接口通常返回JSON格式的数据。在Django项目中，我们可以先将对象处理成字典，然后就可以利用Django封装的<code>JsonResponse</code>向浏览器返回JSON格式的数据，具体的做法如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show_subjects</span>(<span class="params">request</span>):</span></span><br><span class="line">    queryset = Subject.objects.all()</span><br><span class="line">    subjects = []</span><br><span class="line">    <span class="keyword">for</span> subject <span class="keyword">in</span> queryset:</span><br><span class="line">        subjects.append(&#123;</span><br><span class="line">            <span class="string">&#x27;no&#x27;</span>: subject.no,</span><br><span class="line">            <span class="string">&#x27;name&#x27;</span>: subject.name,</span><br><span class="line">            <span class="string">&#x27;intro&#x27;</span>: subject.intro,</span><br><span class="line">            <span class="string">&#x27;isHot&#x27;</span>: subject.is_hot</span><br><span class="line">        &#125;)</span><br><span class="line">    <span class="keyword">return</span> JsonResponse(subjects, safe=<span class="literal">False</span>)</span><br></pre></td></tr></table></figure>

<p>上面的代码中，我们通过循环遍历查询学科得到的<code>QuerySet</code>对象，将每个学科的数据处理成一个字典，在将字典保存在名为<code>subjects</code>的列表容器中，最后利用<code>JsonResponse</code>完成对列表的序列化，向浏览器返回JSON格式的数据。由于<code>JsonResponse</code>序列化的是一个列表而不是字典，所以需要指定<code>safe</code>参数的值为<code>False</code>才能完成对<code>subjects</code>的序列化，否则会产生<code>TypeError</code>异常。</p>
<p>可能大家已经发现了，自己写代码将一个对象转成字典是比较麻烦的，如果对象的属性很多而且某些属性又关联到一个比较复杂的对象时，情况会变得更加糟糕。为此我们可以使用一个名为<code>bpmappers</code>的三方库来简化将对象转成字典的操作，这个三方库本身也提供了对Django框架的支持。</p>
<p>安装三方库<code>bpmappers</code>。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install bpmappers</span><br></pre></td></tr></table></figure>

<p>编写映射器（实现对象到字典转换）。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> bpmappers.djangomodel <span class="keyword">import</span> ModelMapper</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> poll2.models <span class="keyword">import</span> Subject</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SubjectMapper</span>(<span class="params">ModelMapper</span>):</span></span><br><span class="line">   </span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line">        model = Subject</span><br></pre></td></tr></table></figure>

<p>修改视图函数。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show_subjects</span>(<span class="params">request</span>):</span></span><br><span class="line">    queryset = Subject.objects.all()</span><br><span class="line">    subjects = []</span><br><span class="line">    <span class="keyword">for</span> subject <span class="keyword">in</span> queryset:</span><br><span class="line">        subjects.append(SubjectMapper(subject).as_dict())</span><br><span class="line">    <span class="keyword">return</span> JsonResponse(subjects, safe=<span class="literal">False</span>)</span><br></pre></td></tr></table></figure>

<p>配置URL映射。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">urlpatterns = [</span><br><span class="line">    </span><br><span class="line">    path(<span class="string">&#x27;api/subjects/&#x27;</span>, show_subjects),</span><br><span class="line">    </span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>然后访问该接口，可以得到如下所示的JSON格式数据。</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="attr">&quot;no&quot;</span>: <span class="number">1</span>,</span><br><span class="line">        <span class="attr">&quot;name&quot;</span>: <span class="string">&quot;Python全栈+人工智能&quot;</span>,</span><br><span class="line">        <span class="attr">&quot;intro&quot;</span>: <span class="string">&quot;Python是一种计算机程序设计语言。是一种面向对象的动态类型语言，最初被设计用于编写自动化脚本(shell)，随着版本的不断更新和语言新功能的添加，越来越多被用于独立的、大型项目的开发。&quot;</span>,</span><br><span class="line">        <span class="attr">&quot;is_hot&quot;</span>: <span class="literal">true</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 此处省略下面的内容</span></span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>如果不希望在JSON数据中显示学科的成立时间，我们可以在映射器中排除<code>create_date</code>属性；如果希望将是否为热门学科对应的键取名为<code>isHot</code>（默认的名字是<code>is_hot</code>），也可以通过修改映射器来做到。具体的做法如下所示：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> bpmappers <span class="keyword">import</span> RawField</span><br><span class="line"><span class="keyword">from</span> bpmappers.djangomodel <span class="keyword">import</span> ModelMapper</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> poll2.models <span class="keyword">import</span> Subject</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SubjectMapper</span>(<span class="params">ModelMapper</span>):</span></span><br><span class="line">    isHot = RawField(<span class="string">&#x27;is_hot&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line">        model = Subject</span><br><span class="line">        exclude = (<span class="string">&#x27;is_hot&#x27;</span>, )</span><br></pre></td></tr></table></figure>

<p>再次查看学科接口返回的JSON数据。</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="attr">&quot;no&quot;</span>: <span class="number">101</span>,</span><br><span class="line">        <span class="attr">&quot;name&quot;</span>: <span class="string">&quot;Python全栈+人工智能&quot;</span>,</span><br><span class="line">        <span class="attr">&quot;intro&quot;</span>: <span class="string">&quot;Python是一种计算机程序设计语言。是一种面向对象的动态类型语言，最初被设计用于编写自动化脚本(shell)，随着版本的不断更新和语言新功能的添加，越来越多被用于独立的、大型项目的开发。&quot;</span>,</span><br><span class="line">        <span class="attr">&quot;isHot&quot;</span>: <span class="literal">true</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 此处省略下面的内容</span></span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>关于<code>bpmappers</code>详细的使用指南，请参考它的<a target="_blank" rel="noopener" href="https://bpmappers.readthedocs.io/en/stable/">官方文档</a>，这个官方文档是用日语书写的，可以使用浏览器的翻译功能将它翻译成你熟悉的语言即可。</p>
<h3 id="使用Vue-js渲染页面"><a href="#使用Vue-js渲染页面" class="headerlink" title="使用Vue.js渲染页面"></a>使用Vue.js渲染页面</h3><p>接下来我们通过前端框架Vue.js来实现页面的渲染。如果希望全面的了解和学习Vue.js，建议阅读它的<a target="_blank" rel="noopener" href="https://cn.vuejs.org/v2/guide/">官方教程</a>或者在<a target="_blank" rel="noopener" href="https://www.youtube.com/">YouTube</a>上搜索Vue.js的新手教程（Vue.js Crash Course）进行学习。</p>
<p>重新改写subjects.html页面，使用Vue.js来渲染页面。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>学科信息<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="css">		<span class="comment">/* 此处省略层叠样式表 */</span></span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;container&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>扣丁学堂所有学科<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">hr</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;main&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">dl</span> <span class="attr">v-for</span>=<span class="string">&quot;subject in subjects&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">dt</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">a</span> <span class="attr">:href</span>=<span class="string">&quot;&#x27;/static/html/teachers.html?sno=&#x27; + subject.no&quot;</span>&gt;</span></span><br><span class="line">                        &#123;&#123; subject.name &#125;&#125;</span><br><span class="line">                    <span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">img</span> <span class="attr">v-if</span>=<span class="string">&quot;subject.is_hot&quot;</span> <span class="attr">src</span>=<span class="string">&quot;/static/images/hot-icon-small.png&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">dt</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">dd</span>&gt;</span>&#123;&#123; subject.intro &#125;&#125;<span class="tag">&lt;/<span class="name">dd</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">dl</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript">        <span class="keyword">let</span> app = <span class="keyword">new</span> Vue(&#123;</span></span><br><span class="line"><span class="javascript">            el: <span class="string">&#x27;#main&#x27;</span>,</span></span><br><span class="line">            data: &#123;</span><br><span class="line">                subjects: []</span><br><span class="line">            &#125;,</span><br><span class="line">            created() &#123;</span><br><span class="line"><span class="javascript">                fetch(<span class="string">&#x27;/api/subjects/&#x27;</span>)</span></span><br><span class="line"><span class="javascript">                    .then(<span class="function"><span class="params">resp</span> =&gt;</span> resp.json())</span></span><br><span class="line"><span class="javascript">                    .then(<span class="function"><span class="params">json</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">                        <span class="built_in">this</span>.subjects = json</span></span><br><span class="line">                    &#125;)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>前后端分离的开发需要将前端页面作为静态资源进行部署，项目实际上线的时候，我们会对整个Web应用进行动静分离，静态资源通过Nginx或Apache服务器进行部署，生成动态内容的Python程序部署在uWSGI或者Gunicorn服务器上，对动态内容的请求由Nginx或Apache路由到uWSGI或Gunicorn服务器上。</p>
<p>在开发阶段，我们通常会使用Django自带的测试服务器，如果要尝试前后端分离，可以先将静态页面放在之前创建的放静态资源的目录下，具体的做法可以参考<a target="_blank" rel="noopener" href="https://gitee.com/jackfrued/django19062">项目完整代码</a>。</p>
<p>�参考<a target="_blank" rel="noopener" href="https://gitee.com/jackfrued/django19062">项目完整代码</a>。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/django/" rel="tag">django</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-django/Django admin后台管理主题美化"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/django/Django%20admin%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86%E4%B8%BB%E9%A2%98%E7%BE%8E%E5%8C%96/"
    >Django admin后台管理主题美化.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/django/Django%20admin%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86%E4%B8%BB%E9%A2%98%E7%BE%8E%E5%8C%96/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/django/">django</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="Django-admin后台管理主题美化"><a href="#Django-admin后台管理主题美化" class="headerlink" title="Django admin后台管理主题美化"></a>Django admin后台管理主题美化</h1><h2 id="SimpleUI"><a href="#SimpleUI" class="headerlink" title="SimpleUI"></a>SimpleUI</h2><p>源码地址：</p>
<p><strong>码云：</strong><a target="_blank" rel="noopener" href="https://gitee.com/tompeppa/simpleui">https://gitee.com/tompeppa/simpleui</a><br><strong>Github：</strong><a target="_blank" rel="noopener" href="https://github.com/newpanjing/simpleui">https://github.com/newpanjing/simpleui</a></p>
<ol>
<li>下载依赖</li>
</ol>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pip install django-simpleui</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<ol start="2">
<li>配置settings.py</li>
</ol>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">INSTALLED_APPS = [</span><br><span class="line"> <span class="string">&#x27;simpleui&#x27;</span>,</span><br><span class="line"> ....</span><br><span class="line">]</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>重新启动以后你会发现界面已经发生了改变。</p>
<p><img src="https://img-blog.csdnimg.cn/20200320104321704.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzI3NTY1NA==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/20200320104728238.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzI3NTY1NA==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<h2 id="Suit"><a href="#Suit" class="headerlink" title="Suit"></a>Suit</h2><p><strong>1、在你的环境中通过pip安装(注意要安装到项目的环境中)</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install https://github.com/darklow/django-suit/tarball/v2</span><br></pre></td></tr></table></figure>

<p>可以通过pip freeze或者pip list查看结果</p>
<p><strong>2、在apps.py文件中添加：(my_project_app是指你的项目名称，注意更改)</strong></p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># my_project_app/apps.py</span></span><br><span class="line"><span class="keyword">from</span> suit.apps <span class="keyword">import</span> DjangoSuitConfig</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SuitConfig</span>(<span class="params">DjangoSuitConfig</span>):</span></span><br><span class="line">    layout = <span class="string">&#x27;horizontal&#x27;</span></span><br></pre></td></tr></table></figure>

<p>layout这个参数决定你的网页是初始样式是垂直样式还是水平样式，可选参数为‘horizontal’或‘vertical’</p>
<p><strong>3、在settings文件里面，在INSTALLED_APPS中添加以下内容：**</strong>(my_project_app是指你的项目名称，注意更改)**</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">INSTALLED_APPS = (</span><br><span class="line">    ...</span><br><span class="line">    <span class="string">&#x27;my_project_app.apps.SuitConfig&#x27;</span>,  <span class="comment"># 需要添加的内容，要放在&#x27;django.contrib.admin&#x27;之前</span></span><br><span class="line">    <span class="string">&#x27;django.contrib.admin&#x27;</span>,</span><br><span class="line">)</span><br></pre></td></tr></table></figure>

<p>保存以上修改，开启项目，按照url进入admin后台，就可以看到后台已经美化了。</p>
<p><img src="https://pic4.zhimg.com/80/v2-2c5a7f4fc2ca70c82970cc52843b737b_720w.jpg" alt="img"></p>
<p><img src="https://pic2.zhimg.com/80/v2-5a67784cb434b448874bde5ccc415df1_720w.jpg" alt="img"></p>
<h2 id="Grappelli"><a href="#Grappelli" class="headerlink" title="Grappelli"></a>Grappelli</h2><p>快速启动指南</p>
<ul>
<li><p>为了使用Grappelli2.11.1，应该提前安装好Django1.11版本并且Admin 站点管理被激活。</p>
<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2></li>
<li><p>在线安装</p>
</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pip install django-grappelli(此方法将默认安装最新发布版本)</span><br><span class="line">pip install django-grappelli&#x3D;&#x3D;2.11.1(按照自己所需版本进行安装)12</span><br></pre></td></tr></table></figure>

<ul>
<li><p>安装包下载（本地安装），如果需要下载最新的安装包，可前往github进行选择下载（<a target="_blank" rel="noopener" href="https://github.com/sehmaschine/django-grappelli">安装包下载</a>）</p>
<h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2></li>
<li><p>配置文件<strong>settings.py</strong>并且添加<strong>grappelli</strong>到你的<strong>INSTALLED_APPS</strong>中（在django.contrib.admin之前，否则无法生效）</p>
</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">INSTALLED_APPS &#x3D; [</span><br><span class="line">    &#39;grappelli&#39;, #需位于admin APP之前</span><br><span class="line">    &#39;jango.contrib.admin&#39;,</span><br><span class="line">    &#39;django.contrib.auth&#39;,</span><br><span class="line">    &#39;django.contrib.contenttypes&#39;,</span><br><span class="line">    &#39;django.contrib.sessions&#39;,</span><br><span class="line">    ...</span><br><span class="line">]</span><br><span class="line">123456789</span><br></pre></td></tr></table></figure>

<ul>
<li><p><strong>添加URL模式</strong>，The grappelli URLs是相关查找和自动完成所必需的。你的admin管理界面通过定义admin.site的路由信息才得以使用：</p>
<p><strong>django==2.*后的写法</strong></p>
</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">from django.urls import path</span><br><span class="line">urlpatterns &#x3D; [</span><br><span class="line">    path(&#39;grappelli&#x2F;&#39;, include(&#39;grappelli.urls&#39;)), # grappelli URLS</span><br><span class="line">    path(&#39;admin&#x2F;&#39;, admin.site.urls), # admin site</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">focus on： 目前django最新发布版本为2.1系列，2.0之前路由配置使用url()函数，2.0之后使用path()函数；1234567</span><br></pre></td></tr></table></figure>

<ul>
<li><p><strong>添加请求上下文处理器</strong>（仪表板（用词不准）和 用户切换所需）-(项目生成后在setting.py文件中已默认处理)</p>
<p><a target="_blank" rel="noopener" href="http://www.2xkt.com/documents/django_182/topics/templates.html">django1.8中文文档-模板详细介绍</a></p>
</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">TEMPLATES &#x3D; [</span><br><span class="line">    &#123;</span><br><span class="line">        &#39;BACKEND&#39;: &#39;django.template.backends.django.DjangoTemplates&#39;,</span><br><span class="line">        &#39;DIRS&#39;: [os.path.join(BASE_DIR, &#39;templates&#39;)]</span><br><span class="line">        ,</span><br><span class="line">        &#39;APP_DIRS&#39;: True,</span><br><span class="line">        &#39;OPTIONS&#39;: &#123;</span><br><span class="line">            &#39;context_processors&#39;: [</span><br><span class="line"></span><br><span class="line">                &#39;&#39;&#39;</span><br><span class="line"></span><br><span class="line">                &#39;django.template.context_processors.request&#39;,</span><br><span class="line"></span><br><span class="line">                &#39;&#39;&#39;</span><br><span class="line">            ],</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">]123456789101112131415161718</span><br></pre></td></tr></table></figure>

<ul>
<li>收集媒体文件</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python manage.py collectstatic1</span><br></pre></td></tr></table></figure>

<p>focus on:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">在执行collectstatic之前，需要对静态文件进行配置</span><br><span class="line">collectstatic的作用是把所有单独APP下的静态文件收集到同一个地方进行维护，大概配置过程如下：</span><br><span class="line"></span><br><span class="line"> 1. 确保静态文件处理APP存在与于INSTALLED_APPS 中</span><br><span class="line"> INSTALLED_APPS &#x3D; [</span><br><span class="line">    &#39;django.contrib.staticfiles&#39;</span><br><span class="line">]</span><br><span class="line"> 2. 静态文件路由及绝对路径配置</span><br><span class="line"> STATIC_ROOT &#x3D; os.path.join(BASE_DIR, &#39;static&#39;)</span><br><span class="line"> STATIC_URL &#x3D; &#39;&#x2F;static&#x2F;&#39;</span><br><span class="line"></span><br><span class="line">3.用户上传图片及文件等媒体文件配置</span><br><span class="line"> MEDIA_ROOT &#x3D; os.path.join(BASE_DIR, &quot;static&#x2F;media&quot;)</span><br><span class="line"> MEDIA_URL &#x3D; &quot;&#x2F;media&#x2F;&quot;</span><br><span class="line"></span><br><span class="line">4.调试模式下开启静态文件服务</span><br><span class="line">from django.conf.urls import url, include</span><br><span class="line">from django.contrib import admin</span><br><span class="line">from django.conf import settings</span><br><span class="line">from django.conf.urls.static import static</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">urlpatterns &#x3D; [</span><br><span class="line">    url(r&#39;^admin&#x2F;&#39;, admin.site.urls),</span><br><span class="line">]</span><br><span class="line">urlpatterns +&#x3D; static(settings.STATIC_URL, document_root&#x3D;settings.STATIC_ROOT)</span><br><span class="line">urlpatterns +&#x3D; static(settings.MEDIA_URL, document_root&#x3D;settings.MEDIA_ROOT)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">123456789101112131415161718192021222324252627282930</span><br></pre></td></tr></table></figure>

<p>至此配置成功</p>
<ul>
<li><img src="https://img-blog.csdn.net/20180607104714728?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N1bl9kYW5neWFuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="查看"></li>
</ul>
<p><img src="https://img-blog.csdn.net/20180607105030917?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N1bl9kYW5neWFuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="查看"></p>
<h2 id="Xadmin"><a href="#Xadmin" class="headerlink" title="Xadmin"></a>Xadmin</h2><p>xadmin安装</p>
<p>xadmin在github的源码地址<a target="_blank" rel="noopener" href="https://github.com/sshwsfc/xadmin,%E5%8F%AF%E4%BB%A5%E7%94%A8pip%E5%AE%89%E8%A3%85%E4%B9%9F%E5%8F%AF%E4%BB%A5%E8%87%AA%E5%B7%B1%E4%B8%8B%E8%BD%BD%E5%90%8E%E5%AF%BC%E5%85%A5%E5%88%B0%E9%A1%B9%E7%9B%AE%E4%B8%AD%E3%80%82">https://github.com/sshwsfc/xadmin,可以用pip安装也可以自己下载后导入到项目中。</a><br>这里不推荐pip安装，会有报错。本篇介绍下载xadmin后导入的django项目中的方法。</p>
<p>先在github上下载源码包，由于小编的django版本是2.1版本，下载对应的django2分支代码。<br><strong>这里不能直接下载master分支。</strong></p>
<p><strong>下载了后解压出来，只需要里面的xadmin这一部分内容</strong></p>
<p><img src="https://img2020.cnblogs.com/blog/1713532/202004/1713532-20200414155531141-882122585.png" alt="img"></p>
<p> 在原来的django项目下新建一个extra_appa目录，将xadmin文件放入此目录下</p>
<p><strong><img src="https://img2020.cnblogs.com/blog/1713532/202004/1713532-20200414155455635-623741493.png" alt="img"></strong></p>
<p>接着在pycharm上把extra_apps包Mark为Sources Root。具体操作方法：extra_apps右键-找到Mark Directory As-选择Sources Root</p>
<p> <img src="https://img2020.cnblogs.com/blog/1713532/202004/1713532-20200414161242150-113678294.png" alt="img"></p>
<p>在解压的文件里requirements.txt有列出相关的依赖包，用pip安装</p>
<ul>
<li>django-crispy-forms&gt;=1.6.0</li>
<li>django-import-export&gt;=0.5.1</li>
<li>django-reversion&gt;=2.0.0</li>
<li>django-formtools==1.0</li>
<li>future==0.15.2</li>
<li>httplib2==0.9.2</li>
<li>six==1.10.0</li>
</ul>
<p>添加xadmin配置</p>
<p>在项目setting.py中INSTALLED_APPS下注册2个app，xadmin和crispy_forms</p>
<p><img src="https://img2020.cnblogs.com/blog/1713532/202004/1713532-20200414161537699-133982227.png" alt="img"></p>
<p> 找到BASE_DIR这行，修改前代码如下</p>
<p><img src="https://img2020.cnblogs.com/blog/1713532/202004/1713532-20200414161717389-340095278.png" alt="img"></p>
<p> 修改后的代码如下</p>
<p><img src="https://img2020.cnblogs.com/blog/1713532/202004/1713532-20200414161817386-1084287754.png" alt="img"></p>
<p> setting.py设置成中文</p>
<p><img src="https://img2020.cnblogs.com/blog/1713532/202004/1713532-20200414161955877-1458575744.png" alt="img"></p>
<p>urls.py中配置路由</p>
<p><img src="https://img2020.cnblogs.com/blog/1713532/202004/1713532-20200414161900016-1831809193.png" alt="img"></p>
<p>打开xadmin后台</p>
<p>在浏览器输入<a target="_blank" rel="noopener" href="http://localhost:8000/xadmin/%E5%9C%B0%E5%9D%80%EF%BC%8C%E5%B0%B1%E5%8F%AF%E4%BB%A5%E6%89%93%E5%BC%80xadmin%E7%9A%84%E5%90%8E%E5%8F%B0%E4%BA%86">http://localhost:8000/xadmin/地址，就可以打开xadmin的后台了</a></p>
<p><img src="https://img2020.cnblogs.com/blog/1713532/202004/1713532-20200414162152042-43398442.png" alt="img"></p>
<p> 账号和密码和以前admin账号密码一致</p>
<p><img src="https://img2020.cnblogs.com/blog/1713532/202004/1713532-20200414162449862-491106788.png" alt="img"></p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/django/" rel="tag">django</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-docker/10 个冷门但又非常实用的 Docker 使用技巧"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/docker/10%20%E4%B8%AA%E5%86%B7%E9%97%A8%E4%BD%86%E5%8F%88%E9%9D%9E%E5%B8%B8%E5%AE%9E%E7%94%A8%E7%9A%84%20Docker%20%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7/"
    >10 个冷门但又非常实用的 Docker 使用技巧.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/docker/10%20%E4%B8%AA%E5%86%B7%E9%97%A8%E4%BD%86%E5%8F%88%E9%9D%9E%E5%B8%B8%E5%AE%9E%E7%94%A8%E7%9A%84%20Docker%20%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/docker/">docker</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="10-个冷门但又非常实用的-Docker-使用技巧"><a href="#10-个冷门但又非常实用的-Docker-使用技巧" class="headerlink" title="10 个冷门但又非常实用的 Docker 使用技巧"></a>10 个冷门但又非常实用的 Docker 使用技巧</h1><p><img src="http://iubest.gitee.io/pic/640-1601016782724.webp" alt="img"></p>
<p>在平时的工作中，docker 接触得很多，除了经常使用的 <code>docker run</code> ，<code>docker stop</code> 等命令，<code>docker</code> 还有很多十分有用但是却不经常使用的命令，下面就来总结一下：</p>
<h2 id="1-docker-top"><a href="#1-docker-top" class="headerlink" title="1. docker top"></a>1. docker top</h2><p>这个命令是用来查看一个容器里面的进程信息的，比如你想查看一个 <code>nginx</code> 容器里面有几个 <code>nginx</code> 进程的时候，就可以这么做：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ docker top 3b307a09d20d</span><br><span class="line">UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD</span><br><span class="line">root                805                 787                 0                   Jul13               ?                   00:00:00            nginx: master process nginx -g daemon off;</span><br><span class="line">systemd+            941                 805                 0                   Jul13               ?                   00:03:18            nginx: worker process</span><br></pre></td></tr></table></figure>

<h2 id="2-docker-load-amp-amp-docker-save"><a href="#2-docker-load-amp-amp-docker-save" class="headerlink" title="2. docker load &amp;&amp; docker save"></a>2. docker load &amp;&amp; docker save</h2><p>我一般使用这两个命令去下载打包 <code>Kubernetes</code> 的镜像，因为你知道的国内的网速并不像国外那么快。</p>
<p><code>docker save</code> 可以把一个镜像保存到 <code>tar</code> 文件中，你可以这么做：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ docker save registry:2.7.1 &gt;registry-2.7.1.tar</span><br></pre></td></tr></table></figure>

<p>同时 <code>docker load</code> 可以把镜像从 <code>tar</code> 文件导入到 <code>docker</code> 中</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ docker load &lt; registry-2.7.1.tar</span><br></pre></td></tr></table></figure>

<h2 id="3-docker-search"><a href="#3-docker-search" class="headerlink" title="3. docker search"></a>3. docker search</h2><p>这个命令可以帮助你在命令行中方便的搜索 <code>DockerHub</code> 中的镜像，比如:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ docker search nginx</span><br><span class="line">NAME                               DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED</span><br><span class="line">nginx                              Official build of Nginx.                        13519               [OK]</span><br><span class="line">jwilder&#x2F;nginx-proxy                Automated Nginx reverse proxy for docker con…   1846                                    [OK]</span><br><span class="line">richarvey&#x2F;nginx-php-fpm            Container running Nginx + PHP-FPM capable of…   780                                     [OK]</span><br><span class="line">linuxserver&#x2F;nginx                  An Nginx container, brought to you by LinuxS…   123</span><br><span class="line">bitnami&#x2F;nginx                      Bitnami nginx Docker Image                      87                                      [OK]</span><br><span class="line">tiangolo&#x2F;nginx-rtmp                Docker image with Nginx using the nginx-rtmp…   85                                      [OK]</span><br><span class="line">jc21&#x2F;nginx-proxy-manager           Docker container for managing Nginx proxy ho…   73</span><br><span class="line">alfg&#x2F;nginx-rtmp                    NGINX, nginx-rtmp-module and FFmpeg from sou…   71                                      [OK]</span><br><span class="line">nginxdemos&#x2F;hello                   NGINX webserver that serves a simple page co…   57                                      [OK]</span><br><span class="line">jlesage&#x2F;nginx-proxy-manager        Docker container for Nginx Proxy Manager        53                                      [OK]</span><br><span class="line">nginx&#x2F;nginx-ingress                NGINX Ingress Controller for Kubernetes         37</span><br><span class="line">......</span><br></pre></td></tr></table></figure>

<p>当然这个功能在国内可能不会特别好用，因为……</p>
<h2 id="4-docker-events"><a href="#4-docker-events" class="headerlink" title="4. docker events"></a>4. docker events</h2><p>这个命令可以帮助你实时获取 <code>docker</code> 的各种事件信息，比如创建了一个容器什么的。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ docker events</span><br><span class="line">2020-07-28T21:28:46.000403018+08:00 image load sha256:432bf69f0427b52cad10897342eaf23521b7d973566354118e9a59c4d31b5fae (name&#x3D;sha256:432bf69f0427b52cad10897342eaf23521b7d973566354118e9a59c4d31b5fae)</span><br></pre></td></tr></table></figure>

<h2 id="5-docker-update"><a href="#5-docker-update" class="headerlink" title="5. docker update"></a>5. docker update</h2><p>当你 <code>docker run</code> 了之后却发现里面有一些参数并不是你想要的状态比如你设置的 <code>nginx</code>容器 <code>cpu</code> 或者内存太小，这个时候你就可以使用 <code>docker update</code> 去修改这些参数。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ docker update nginx --cpus 2</span><br></pre></td></tr></table></figure>

<h2 id="6-docker-history"><a href="#6-docker-history" class="headerlink" title="6. docker history"></a>6. docker history</h2><p>当你修改了一个镜像，但是忘记了每一层的修改命令，或者你想查看一个镜像是怎么构建的时候就可以使用这个命令，比如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ docker history  traefik:v2.1.6</span><br><span class="line">IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT</span><br><span class="line">5212a87ddaba        5 months ago        &#x2F;bin&#x2F;sh -c #(nop)  LABEL org.opencontainers.…   0B</span><br><span class="line">&lt;missing&gt;           5 months ago        &#x2F;bin&#x2F;sh -c #(nop)  CMD [&quot;traefik&quot;]              0B</span><br><span class="line">&lt;missing&gt;           5 months ago        &#x2F;bin&#x2F;sh -c #(nop)  ENTRYPOINT [&quot;&#x2F;entrypoint.…   0B</span><br><span class="line">&lt;missing&gt;           5 months ago        &#x2F;bin&#x2F;sh -c #(nop)  EXPOSE 80                    0B</span><br><span class="line">&lt;missing&gt;           5 months ago        &#x2F;bin&#x2F;sh -c #(nop) COPY file:59a219a1fb7a9dc8…   419B</span><br><span class="line">&lt;missing&gt;           5 months ago        &#x2F;bin&#x2F;sh -c set -ex;  apkArch&#x3D;&quot;$(apk --print-…   52.9MB</span><br><span class="line">&lt;missing&gt;           5 months ago        &#x2F;bin&#x2F;sh -c apk --no-cache add ca-certificate…   1.85MB</span><br><span class="line">&lt;missing&gt;           6 months ago        &#x2F;bin&#x2F;sh -c #(nop)  CMD [&quot;&#x2F;bin&#x2F;sh&quot;]              0B</span><br><span class="line">&lt;missing&gt;           6 months ago        &#x2F;bin&#x2F;sh -c #(nop) ADD file:a1906f14a4e217a49…   4.81MB</span><br></pre></td></tr></table></figure>

<h2 id="7-docker-wait"><a href="#7-docker-wait" class="headerlink" title="7. docker wait"></a>7. docker wait</h2><p>这个命令可以查看容器的退出状态，比如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ docker wait 7f7f0522a7d0</span><br><span class="line">0</span><br></pre></td></tr></table></figure>

<p>这样你就可以知道这个容器是正常退出的还是异常退出的了。</p>
<h2 id="8-docker-pause-amp-amp-docker-unpause"><a href="#8-docker-pause-amp-amp-docker-unpause" class="headerlink" title="8. docker pause &amp;&amp; docker unpause"></a>8. docker pause &amp;&amp; docker unpause</h2><p>当你运行了一个容器但是想要暂停它运行的时候，你就可以使用这个命令。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ docker pause 7f7f0522a7d0</span><br></pre></td></tr></table></figure>

<h2 id="9-docker-diff"><a href="#9-docker-diff" class="headerlink" title="9. docker diff"></a>9. docker diff</h2><p>当你运行了一个容器，但是你不知道容器里修改了哪一些文件的时候可以使用这个命令，比如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ docker diff 38c59255bf6e</span><br><span class="line">C &#x2F;etc</span><br><span class="line">A &#x2F;etc&#x2F;localtime</span><br><span class="line">C &#x2F;var</span><br><span class="line">C &#x2F;var&#x2F;lib</span><br><span class="line">A &#x2F;var&#x2F;lib&#x2F;registry</span><br></pre></td></tr></table></figure>

<h2 id="10-docker-stats"><a href="#10-docker-stats" class="headerlink" title="10. docker stats"></a>10. docker stats</h2><p>这个是 <code>docker</code> 内置的监控命令，当你想要查看当前主机下所有容器占用内存和 <code>cpu</code> 的情况的时候就可以使用这个命令。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ docker stats</span><br><span class="line"></span><br><span class="line">CONTAINER ID        NAME                           CPU %               MEM USAGE &#x2F; LIMIT     MEM %               NET I&#x2F;O             BLOCK I&#x2F;O           PIDS</span><br><span class="line">1c5ade04e7f9        redis                          0.08%               17.53MiB &#x2F; 47.01GiB   0.04%               10.9GB &#x2F; 37GB       0B &#x2F; 0B             4</span><br><span class="line">afe6d4ebe409        kafka-exporter                 0.09%               16.91MiB &#x2F; 47.01GiB   0.04%               1.97GB &#x2F; 1.53GB     752MB &#x2F; 0B          23</span><br><span class="line">f0c7c01a9c34        kafka-docker_zookeeper         0.01%               308.8MiB &#x2F; 47.01GiB   0.64%               20.2MB &#x2F; 12.2MB     971MB &#x2F; 3.29MB      28</span><br><span class="line">da8c5008955f        kafka-docker_kafka-manager     0.08%               393.2MiB &#x2F; 47.01GiB   0.82%               1.56MB &#x2F; 2.61MB     1.14GB &#x2F; 0B         60</span><br><span class="line">c8d51c583c49        kafka-docker_kafka             1.63%               1.256GiB &#x2F; 47.01GiB   2.67%               30.4GB &#x2F; 48.9GB     22.3GB &#x2F; 5.77GB     85</span><br><span class="line">......</span><br></pre></td></tr></table></figure> 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/docker/" rel="tag">docker</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-docker/Docker 容器间的免密登陆"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/docker/Docker%20%E5%AE%B9%E5%99%A8%E9%97%B4%E7%9A%84%E5%85%8D%E5%AF%86%E7%99%BB%E9%99%86/"
    >Docker 容器间的免密登陆.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/docker/Docker%20%E5%AE%B9%E5%99%A8%E9%97%B4%E7%9A%84%E5%85%8D%E5%AF%86%E7%99%BB%E9%99%86/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/docker/">docker</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="Docker-容器间的免密登陆"><a href="#Docker-容器间的免密登陆" class="headerlink" title="Docker 容器间的免密登陆"></a>Docker 容器间的免密登陆</h1><h2 id="拉取Centos镜像"><a href="#拉取Centos镜像" class="headerlink" title="拉取Centos镜像"></a>拉取Centos镜像</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker pull centos:7</span><br><span class="line">1</span><br></pre></td></tr></table></figure>

<h2 id="启动2个容器"><a href="#启动2个容器" class="headerlink" title="启动2个容器"></a>启动2个容器</h2><p>注意–privileged init 不能省略，否则不能使用systemctl</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">docker run -itd --name master   --privileged  centos init</span><br><span class="line">docker run -itd --name slave   --privileged  centos init</span><br><span class="line">12</span><br></pre></td></tr></table></figure>

<h2 id="分别进入2个容器"><a href="#分别进入2个容器" class="headerlink" title="分别进入2个容器"></a>分别进入2个容器</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">docker exec -it master &#x2F;bin&#x2F;bash</span><br><span class="line">docker exec -it slave &#x2F;bin&#x2F;bash</span><br><span class="line">12</span><br></pre></td></tr></table></figure>

<h2 id="安装ssh服务并启动"><a href="#安装ssh服务并启动" class="headerlink" title="安装ssh服务并启动"></a>安装ssh服务并启动</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">yum -y install openssh-server openssh-clients</span><br><span class="line">systemctl start sshd</span><br><span class="line">systemctl enable sshd</span><br><span class="line">123</span><br></pre></td></tr></table></figure>

<h2 id="生成秘钥"><a href="#生成秘钥" class="headerlink" title="生成秘钥"></a>生成秘钥</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa</span><br><span class="line">1</span><br></pre></td></tr></table></figure>

<p>三次回车</p>
<h2 id="slave容器中"><a href="#slave容器中" class="headerlink" title="slave容器中"></a>slave容器中</h2><p>注意，authorized_keys文件的权限为600，不知道为啥，就是这么规定的。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">touch &#x2F;root&#x2F;.ssh&#x2F;authorized_keys</span><br><span class="line">chmod 600 &#x2F;root&#x2F;.ssh&#x2F;authorized_keys</span><br><span class="line">12</span><br></pre></td></tr></table></figure>

<h2 id="master容器中"><a href="#master容器中" class="headerlink" title="master容器中"></a>master容器中</h2><p>/root/.ssh/id_rsa.pub是公钥文件，把它的内容复制到slave容器的/root/.ssh/authorized_keys中，就可以实现免密登录slave容器了。<br>也可以用简便方法拷贝：<br>需要输入一次slave的密码</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ssh-copy-id -i ~&#x2F;.ssh&#x2F;id_rsa.pub [slave的IP地址]</span><br><span class="line">1</span><br></pre></td></tr></table></figure>

<h2 id="S最后测试"><a href="#S最后测试" class="headerlink" title="S最后测试"></a>S最后测试</h2><p>master容器直接ssh [slave的IP地址]，就可以免密登录啦。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/docker/" rel="tag">docker</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-docker/Docker容器网络必知必会"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/docker/Docker%E5%AE%B9%E5%99%A8%E7%BD%91%E7%BB%9C%E5%BF%85%E7%9F%A5%E5%BF%85%E4%BC%9A/"
    >Docker容器网络必知必会.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/docker/Docker%E5%AE%B9%E5%99%A8%E7%BD%91%E7%BB%9C%E5%BF%85%E7%9F%A5%E5%BF%85%E4%BC%9A/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/docker/">docker</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="Docker容器网络必知必会"><a href="#Docker容器网络必知必会" class="headerlink" title="Docker容器网络必知必会"></a>Docker容器网络必知必会</h1><blockquote>
<blockquote>
<p>来源：<a target="_blank" rel="noopener" href="https://www.cnblogs.com/sally-zhou/p/13424208.html">https://www.cnblogs.com/sally-zhou/p/13424208.html</a></p>
</blockquote>
</blockquote>
<p>Docker的技术依赖于Linux内核的虚拟化技术的发展，Docker使用到的网络技术有Network Namespace、Veth设备对、Iptables/Netfilter、网桥、路由等。接下来，我将以Docker容器网络实现的基础技术来分别阐述，希望给大家能形成一个稳固的基础知识网。</p>
<h3 id="Network-Namespace"><a href="#Network-Namespace" class="headerlink" title="Network Namespace"></a>Network Namespace</h3><p>为了支持网络协议栈的多个实例，Linux在网络栈引入了Network Namespace，这些独立的协议栈被隔离到不同的Namespace中，处于不同Namespace中的网络栈是完全隔离的，彼此无法通信。具体有关Linux Namespace的介绍，可以另行浏览之前写的《<a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzI1OTY2MzMxOQ==&mid=2247484877&idx=1&sn=54180c2205e82ead217873a6b5f92965&chksm=ea743175dd03b863364312582024e8315d4a4a94646291d453a1a720e370ac2b76f143add81b&scene=21#wechat_redirect">Linux Namespace</a>》。</p>
<p>Linux的网络协议栈十分复杂，为了支持独立的协议栈，相关的全局变量都必须修改为协议栈私有。Linux实现Network Namespace的核心就是让这些全局变量称为Network Namespace变量的成员，然后为协议栈的函数调用加入一个Namespace参数。与此同时，为了保证已开发程序及内核代码的兼容性，内核代码隐式地使用了Namespace空间内的变量。应用程序如果没有对Namespace有特殊需求，那么不需要额外的代码，Network Namespace对应用程序而言是透明的。</p>
<p>在建立了新的Network Namespace，并将某个进程关联到这个网络命名空间后，就出现了如下的命名空间下的内核数据结构，所有网络栈变量都放入了Network Namespace的数据结构中，这个Network Namespace是属于它进程组私有的，与其他进程组不冲突。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601000573124.webp" alt="null"></p>
<p>Docker正是利用了Network Namespace特性，实现了不同容器之间的网络隔离。如果一个容器声明使用宿主机的网络栈（-net = host），即不开启Network Namespace，例如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run –d –net&#x3D;host --name c_name i_name</span><br></pre></td></tr></table></figure>

<p>这种情况下，这个容器启动之后监听的是宿主机的80端口。像这样直接使用宿主机网络栈的方式，虽然可以为容器提供良好的网络性能，但也不可避免的造成端口冲突等网络资源冲突的问题。</p>
<p>所以在一般情况下，我们都希望程序引入Network Namespace里的网络栈，即这个容器拥有自己的IP和端口。但是，这个时候也会带来一个新的问题，被隔离的容器进程，是怎么与其它被隔离的进程进行网络通信的？</p>
<h3 id="Net-Bridge"><a href="#Net-Bridge" class="headerlink" title="Net Bridge"></a>Net Bridge</h3><p>上面说到，Linux 可以支持不同的网络，他们之间是怎么支持够互相通信的呢？如果是两台主机，那需要的可能只是一根网线，把它们连接在一台交换机上。而在Linux当中，网桥（Bridge）就起到相应的作用。本质上来说，这是一个数据链路层（data link）的设备，根据Mac地址的信息转发到网桥的不同端口上。而Docker就是在宿主机上默认创建一个docker0的网桥，凡是连接docker0的网桥，都可以用它来通信。</p>
<h4 id="细述Bridge"><a href="#细述Bridge" class="headerlink" title="细述Bridge"></a>细述Bridge</h4><p>网桥是一个二层的虚拟网络设备，把若干个网络接口“连接”起来，使得网口之间的报文可以转发。网桥能够解析收发的报文，读取目标的Mac地址信息，和自己的Mac地址表结合，来决策报文转发的目标网口。为了实现这些功能，网桥会学习源Mac地址。在转发报文时，网桥只需要向特定的端口转发，从而避免不必要的网络交互。如果它遇到了一个自己从未学过的地址，就无法知道这个报文应该向哪个网口转发，就将报文广播给除了报文来源之外的所有网口。</p>
<p>在实际网络中，网络拓扑不可能永久不变。如果设备移动到另一个端口上，而它没有发送任何数据，那么网桥设备就无法感知到这个变化，结果网桥还是向原来的端口发数据包，在这种情况下数据就会丢失。所以网桥还要对学习到的Mac地址表加上超时时间，默认5min。如果网桥收到了对应端口MAC地址回发的包。则重置超时时间，否则过了超时时间后，就认为哪个设备不在那个端口上了，他就会广播重发。</p>
<p>Linux为了支持越来越多的网卡以及虚拟设备，所以使用网桥去提供这些设备之间转发数据的二层设备。Linux内核支持网口的桥接（以太网接口），这与单纯的交换机还是不太一样，交换机仅仅是一个二层设备，对于接受到的报文，要么转发，要么丢弃。运行着Linux内核的机器本身就是一台主机，有可能是网络报文的目的地，其收到的报文要么转发，要么丢弃，还可能被送到网络协议的网络层，从而被自己主机本身的协议栈消化，所以我们可以把网桥看作一个二层设备，也可以看做是一个三层设备。</p>
<h4 id="Linux中Bridge实现"><a href="#Linux中Bridge实现" class="headerlink" title="Linux中Bridge实现"></a>Linux中Bridge实现</h4><p>Linux内核是通过一个虚拟的网桥设备（Net Device）来实现桥接的。这个虚拟设备可以绑定若干个以太网接口，从而将它们连接起来。Net Device网桥和普通的设备不同，最明显的是它还可以有一个ip地址。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601000572955.png" alt="null"></p>
<p>如上图所示，网桥设备br0绑定的eth0和eth1。对于网络协议栈的上层来说，只看到br0。因为桥接是在数据链路层实现的，上层不需要关心桥接的细节，于是协议栈上层需要发送的报文被送到br0，网桥设备的处理代码判断报文被转发到eth0还是eth1，或者两者皆转发。反之，从eth0或者从eth1接收到的报文被提交给网桥的处理代码，在这里判断报文应该被转发、丢弃或者提交到协议栈上层。</p>
<p>而有时eth0、eth1也可能会作为报文的源地址或目的地址，直接参与报文的发送和接收，从而绕过网桥。</p>
<h4 id="Bridge常用操作"><a href="#Bridge常用操作" class="headerlink" title="Bridge常用操作"></a>Bridge常用操作</h4><p>Docker自动完成了对网桥的创建和维护。如果想要进一步理解网桥，可以看下如下举的一些常用操作命令。</p>
<p>新增一个网桥：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brctl addbr xxxxx</span><br></pre></td></tr></table></figure>

<p>在新增网桥的基础上增加网口，在linux中，一个网口其实就是一个物理网卡。将物理网卡和网桥连接起来：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brctl addif xxxx ethx</span><br></pre></td></tr></table></figure>

<p>网桥的物理网卡作为一个网口，由于在链路层工作，就不再需要IP地址了，这样上面的IP地址自然失效：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipconfig ethx 0.0.0.0</span><br></pre></td></tr></table></figure>

<p>给网桥配置一个IP地址：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipconfig brxxx xxx.xxx.xxx.xxx</span><br></pre></td></tr></table></figure>

<p>这样网桥就是一个有了IP地址，而连接在这之上的网卡就是一个纯链路层设备了。</p>
<h3 id="Veth-Pair"><a href="#Veth-Pair" class="headerlink" title="Veth Pair"></a>Veth Pair</h3><p>上文说到，docker在宿主机上创建docker0网桥后，凡是连接到docker0上的网桥，就可以用它来通信。那么这里又有个问题，就是这些容器是如何连接到docker0网桥上的？所以这就是Veth Pair虚拟设备的作用了，Veth Pair就是为了在不同的Network Namespace之间进行通信，利用它，可以将两个Network Namespace连接起来。</p>
<p>Veth Pair设备的特点是：它被创建出来后，总是以两张虚拟网卡（Veth Peer）的形式出现。并且，其中一个网卡发出的数据包，可以直接出现在另一张“网卡”上，哪怕这两张网卡在不同的Network Namespace中。</p>
<p>正是因为这样的特点，Veth Pair成对出现，很像是一对以太网卡，常常被看做是不同Network Namespace直连的“网线”。在Veth一端发送数据时，他会将数据发送到另一端并触发另一端的接收操作。我们可以把Veth Pair其中一端看做另一端的一个Peer。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601000573048.png" alt="null"></p>
<h4 id="Veth-Pair操作命令"><a href="#Veth-Pair操作命令" class="headerlink" title="Veth Pair操作命令"></a>Veth Pair操作命令</h4><p>创建Veth Pair：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip link add veth0 type veth peer name veth1</span><br></pre></td></tr></table></figure>

<p>创建后查看Veth Pair的信息：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip link show</span><br></pre></td></tr></table></figure>

<p>将其中一个Veth Peer设置到另一个Namespace：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip link set veth1 netns netns1</span><br></pre></td></tr></table></figure>

<p>在netns1中查看veth1设备：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip netns exec netns1 ip link show</span><br></pre></td></tr></table></figure>

<p>当然，在docker里面，除了将Veth放入容器，还改名为eth0。想要通信必须先分配IP地址：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip netns exec netns1 ip addr add 10.1.1.1&#x2F;24 dev veth1ip addr add 10.1.1.2&#x2F;24 dev veth0</span><br></pre></td></tr></table></figure>

<p>启动它们：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip netns exec netns1 ip link set dev veth1 upip link set dev veth0 up</span><br></pre></td></tr></table></figure>

<p>测试通信:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip netns exec netns1 ping 10.1.1.2</span><br></pre></td></tr></table></figure>

<h4 id="Veth-Pair查看端对端"><a href="#Veth-Pair查看端对端" class="headerlink" title="Veth Pair查看端对端"></a>Veth Pair查看端对端</h4><p>在实际操作Veth Pair时，可以使用ethtool便于操作。</p>
<p>在一个Namespace中查看Veth Pair接口在设备列表中的序列号：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip netns exec netns1 ethtool -S veth1</span><br></pre></td></tr></table></figure>

<p>如果得知另一端的接口设备序列号，假如序列号为6，则可以继续查看6代表了什么设备：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip netns exec netns2 ip link | grep 6</span><br></pre></td></tr></table></figure>

<h3 id="Iptables-Netfilter"><a href="#Iptables-Netfilter" class="headerlink" title="Iptables/Netfilter"></a>Iptables/Netfilter</h3><p>Linux协议栈非常高效且复杂。如果我们想要在数据处理过程中对关心的数据进行一些操作，则需要Linux提供一套相应的机制帮助用户实现自定义的数据包处理过程。</p>
<p>在Linux网络协议栈有一组网络回调函数挂接点，通过这些挂接点函数挂接的钩子函数可以在Linux网络栈处理数据包的过程中对数据包一些操作，例如过滤、修改、丢弃等。整个挂接点技术叫做Iptables和Netfilter。</p>
<p>Netfilter负责在内核中执行各种各样的挂接规则，运行在内核模式中。而Iptables是在用户模式下运行的进程，负责协助维护内核中Netfilter的各种规则表。通过二者的配合来实现整个Linux网络协议栈中灵活的数据包处理机制。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601000573001.webp" alt="null"></p>
<h4 id="规则表Table"><a href="#规则表Table" class="headerlink" title="规则表Table"></a>规则表Table</h4><p>这些挂载点能挂接的规则也分不同的类型，目前主要支持的Table类型如下：</p>
<p>•RAW•MANGLE•NAT•FILTER</p>
<p>上述4个规则链的优先级是RAW最高，FILTER最低。</p>
<p>在实际应用中，不同挂接点需要的规则类型通常不同。例如，在Input的挂接点上明显不需要FILTER的过滤规则，因为根据目标地址，已经在本机的上层协议栈了，所以无需再挂载FILTER过滤规则。</p>
<h3 id="Route"><a href="#Route" class="headerlink" title="Route"></a>Route</h3><p>Linux系统包含了一个完整的路由功能。当IP层在处理数据发送或者转发时，会使用路由表来决定发往哪里。通常情况下，如果主机与目的主机直接相连，那么主机可以直接发送IP报文到目的主机。</p>
<p>路由功能是由IP层维护的一张路由表来实现。当主机收到数据报文时，它用此表来决策接下来应该做什么操作。当从网络侧接收到数据报文时，IP层首先会检查报文的IP地址是否与主机自身的地址相同。如果数据报文中的IP地址是自身主机的地址，那么报文将被发送到传输层相应的协议栈中去。如果报文中的IP地址不是主机自身的地址，并且配置了路由功能，那么报文将被转发，否则报文将被丢弃。</p>
<p>路由表的数据一般以条目形式存在，一个典型的路由表条目通常包含以下主要的条目项：</p>
<p>•目的IP地址•下一个路由器的IP地址•标志•网络接口规范</p>
<p>通过路由表转发时，如果任何条目的第一个字段完全匹配目标条目的IP地址（主机）或部分匹配（网络），那么它将指示下一个路由器的IP地址。这些信息将告诉主机数据包该转发到哪一个“下一个路由器”。而条目中所有其它字段将提供更多的辅助信息来为路由转发做决定。</p>
<p>如果没有一个完全匹配的IP，则继续搜索网络ID。找到则转发数据到指定路由器上。由此可知，网络上所有主机都是通过这个路由表中的单个条目进行管理。</p>
<p>如果上述两个条件都不匹配，则将数据报文转发到一个默认路由器上。</p>
<p>如果上述步骤失败，默认路由器也不存在，那么这个数据报文无法转发。任何无法投递的数据都会产生一个ICMP主机不可达或者ICMP网络不可达的错误，并将该错误返回给生成此数据的应用程序。</p>
<h4 id="Route-Table"><a href="#Route-Table" class="headerlink" title="Route Table"></a>Route Table</h4><p>Linux路由表至少2个，一个是LOCAL，一个是MAIN。</p>
<p>Local表用于供Linux协议栈识别本地地址，以及进行本地各个不同网络之间的数据转发。MAIN表用于各类网络IP的转发。它的建立既可以使用静态配置生成，也可以使用动态路由发现协议生成。动态路由发现协议一般使用组播功能来通过发送路由发现数据，动态获取和交换网络的路由信息，并更新到路由表中。</p>
<p>通过下列命令查看LOCAL表的内容：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip route show table local type local</span><br></pre></td></tr></table></figure>

<p>路由表的查看：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip route list</span><br></pre></td></tr></table></figure>

<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>到此为止，介绍了实现docker容器网络最核心的基础部分，包括Network Namespace、Bridge、Veth Pair、Iptables/Netfilter、Route。希望给大家一个全面的了解。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/docker/" rel="tag">docker</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
  </article>
  

  
  <nav class="page-nav">
    
    <a class="extend prev" rel="prev" href="/page/3/">上一页</a><a class="page-number" href="/">1</a><a class="page-number" href="/page/2/">2</a><a class="page-number" href="/page/3/">3</a><span class="page-number current">4</span><a class="page-number" href="/page/5/">5</a><a class="page-number" href="/page/6/">6</a><span class="space">&hellip;</span><a class="page-number" href="/page/14/">14</a><a class="extend next" rel="next" href="/page/5/">下一页</a>
  </nav>
  
</section>
</div>

      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2020
        <i class="ri-heart-fill heart_icon"></i> TzWind
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        由 <a href="https://hexo.io" target="_blank">Hexo</a> 强力驱动
        <span class="division">|</span>
        主题 - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="Hexo"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" target="_blank" rel="noopener" href="http://www.baidu.com">百度</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/2019/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>